react零基础入门到项目实战
千锋教育前端React全家桶视频教程_react零基础入门到项目实战完整版
React全家桶
- React全家桶
- 一. React介绍
- 1.React起源与发展
- 2.React与传统MVC的关系
- 3.React的特性
- 4.虚拟DOM
- 三、编写第一个react应用程序
- 四. JSX语法与组件
- 1. JSX语法
- 2. 组件的创建
- 2.1类组件(Class组件)
- 2.2函数式组件
- 3嵌套
- 4.组件的样式
- 5. 事件处理
- 5.1、绑定事件
- 5.2、事件handler的写法
- 5.3、Event 对象
- 6.Ref的应用
- 6.1给标签设置ref="username"
- 6.2 给组件设置ref="username"
- 6.3 新的写法
- 五、组件的数据挂载方式
- 1、状态(state)
- (1) 定义state
- (2) setState
- 2、属性(props)
- 2.1属性初识
- 2.2属性验证
- 2.3默认属性
- 3、属性vs状态
- 4、渲染数据
- 六、表单中的受控组件与非受控组件
- 1、非受控组件
- 2、受控组件
- 七. 组件通信的方式
- 1. 父子组件通信方式
- 2. 非父子组件通信方式
- (1) 状态提升(中间人模式) 06-中间人模式.js
- (2) 发布订阅模式实现 08-订阅发布模式案例.js
- (3) context状态树传参 09-context.js
- 八. 插槽
- 九. React生命周期
- 十. React Hooks
- 1.1useState(保存组件状态)
- 1.2useEffect(处理副作用)和useLayoutEffect (同步执行副作用)
- 1.3useCallback(记忆函数)
- 1.4 useMemo 记忆组件
- 1.5 自定义hooks
rcc 类组件
rfc 函数式
React全家桶
一. React介绍
1.React起源与发展
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
2.React与传统MVC的关系
轻量级的视图层库!A JavaScript library for building user interfaces
React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
3.React的特性

4.虚拟DOM


二. create-react-app
全局安装create-react-app
npm install -g create-react-app
创建一个项目
$ create-react-app your-app 注意命名方式
Creating a new React app in /dir/your-app. Installing packages. This might take a couple of minutes. 安装过程较慢,
Installing react, react-dom, and react-scripts…
如果不想全局安装,可以直接使用npx
npx create-react-app myapp 也可以实现相同的效果
这需要等待一段时间,这个过程实际上会安装三个东西
- react: react的顶级库
- react-dom: 因为react有很多的运行环境,比如app端的react-native, 我们要在web上运行就使用react-dom
- react-scripts: 包含运行和打包react应用程序的所有脚本及配置
出现下面的界面,表示创建项目成功:
Success! Created your-app at /dir/your-app
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd your-app
npm start
Happy hacking!
根据上面的提示,通过 cd your-app 命令进入目录并运行 npm start 即可运行项目。
生成项目的目录结构如下:

常见问题:
npm安装失败
- 切换为npm镜像为淘宝镜像
- 使用yarn,如果本来使用yarn还要失败,还得把yarn的源切换到国内
- 如果还没有办法解决,请删除node_modules及package-lock.json然后重新执行 npm install命令
- 再不能解决就删除node_modules及package-lock.json的同时清除npm缓存 npm cache clean --force 之后再执行 npm install 命令
三、编写第一个react应用程序
react开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,createreact-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在
里面重新创建一个index.js. 写入以下代码:
// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有 一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中 引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'
// ReactDOM里有一个render方法,功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的 元素上
ReactDOM.render(
// 这里就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。语 法错误吗?这并不是合法的 JavaScript 代码, “在 JavaScript 写的标签的”语法叫 JSX- JavaScript XML。
<h1>欢迎进入React的世界</h1>, // 渲染到哪里
document.getElementById('root') )
四. JSX语法与组件
1. JSX语法
JSX 将 HTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。
在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让代码更加直观并易于维护。 编译过程由Babel 的 JSX 编译器实现。
https://reactjs.org/docs/hello-world.html
原理是什么呢?
要明白JSX的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?
看下面的DOM结构
<div class='app' id='appRoot'>
<h1 class='title'>欢迎进入React的世界</h1>
<p>
React.js 是一个帮助你构建页面 UI 的库
</p>
</div>
上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:
{
tag: 'div',
attrs: { className: 'app', id: 'appRoot'},
children: [
{ tag: 'h1',
attrs: { className: 'title' },
children: ['欢迎进入React的世界']
},
{ tag: 'p', attrs: null,
children: ['React.js 是一个构建页面 UI 的库']
}
]
}
但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX结构转换成 JavaScript 的对象结构。
下面代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
<div className='app' id='appRoot'>
<h1 className='title'>欢迎进入React的世界</h1>
<p>
React.js 是一个构建页面 UI 的库 </p>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
编译之后将得到这样的代码:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render() {
return (
React.createElement("div", {
className: 'app', id: 'appRoot'
},
React.createElement("h1", { className: 'title' }, "欢迎进入React的世界"),
React.createElement("p", null, "React.js 是一个构建页面 UI 的库")
)
)
}
}
ReactDOM.render(React.createElement(App), document.getElementById('root'))
React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等, 语法为
React.createElement( type, [props], [...children] )
所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:
JSX —使用react构造组件,bable进行编译—> JavaScript对象 — ReactDOM.render() —>DOM元素 —>插入页面

2. 组件的创建
2.1类组件(Class组件)
ES6的加入让JavaScript直接支持使用class来定义一个类,react创建组件的方式就是使用的类的继承,
ES6 class 是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:
src\01-base\01-class组件.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
// class Test {
// constructor(){
// this.a=1;
// }
// testa(){
// console.log("testa")
// }
// }
// class ChildTest extends Test{
// testb(){
// console.log("testb")
// }
// }
// var obj = new ChildTest()
// obj.testa()
// console.log(obj.a)
import React from 'react'
//es6 class 组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:
//使用() 进行格式化,样式好看
class KerwinApp extends React.Component {
render() {
return (
<div>
hello react Component
<ul>
<li>1111</li>
<li>2222</li>
</ul>
</div>
)
}
}
export default KerwinApp
src\index.js
// // 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有 一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react';
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中 引入的,而不是从 react 引入。
// import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
// ReactDOM.render(<div>1111</div>,document.getElementById("root"))
// ReactDOM.render(React.createElement(
// "div",{
// id:"aaaa",
// class:"bbbb"
// },11111
// ),document.getElementById("root"))
import App from './01-base/01-class组件'
// ReactDOM.render(<App/>,document.getElementById("root"))
// ReactDOM.createRoot(document.getElementById('root')).render(<App/>)
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
2.2函数式组件
src\01-base\02-函数式组件.js
function App(){
return (
<div>
hello functional component
<div>1111</div>
<div>2222</div>
</div>
)
}
/*
16.8之前 //无状态组件
16.8之后 react hooks
*/
export default App
这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。
import App from ‘./01-base/02-函数式组件’
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './01-base/02-函数式组件'
// ReactDOM.render(<App/>,document.getElementById("root"))
// ReactDOM.createRoot(document.getElementById('root')).render(<App/>)
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
3嵌套
\src\01-base\03-组件的嵌套.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Child extends Component{
render(){
return <div>child</div>
}
}
class Navbar extends Component{
render(){
return <div>navbar
<Child></Child>
</div>
}
}
function Swiper(){
return <div>swiper</div>
}
const Tabbar = ()=><div>tabbar</div>
export default class App extends Component {
render() {
return (
<div>
<Navbar>
</Navbar>
<Swiper></Swiper>
<Tabbar></Tabbar>
</div>
)
}
}
4.组件的样式
- 行内样式
想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:
// 注意这里的两个括号,第一个表示我们在要JSX里插入JS了,第二个是对象的括号
<p style={{color:'red', fontSize:'14px'}}>Hello world</p>
行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如 render 函数里、组件原型上、外链js文件中
- 使用 class
React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是, class 需要写成
className (因为毕竟是在写类js代码,会收到js规则的现在,而 class 是关键字)
<p className="hello">Hello world</p>
注意:
class ==> className , for ==> htmlFor(label)
04-组件的样式.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import './css/01-index.css' //导入 css模块, webpack的支持。
export default class App extends Component {
render() {
var myname = "kerwin"
var obj = {
// 驼峰写法
backgroundColor:"yellow",
fontSize:"30px"
}
return (
<div>
{10+20}-{myname}
{10>20?'aaa':'bbb'}
{/* 只有{}里面才能放变量,style需要配置对象 */}
<div style={obj}>11111111111</div>
<div style={{background:"red"}}>2222222222</div>
{
/*React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体
*/
}
{/* 引用外部链接 */}
<div className="active">3333333333333333333333</div>
<div id="myapp">5555555555555555</div>
<label htmlFor="username">用户名:</label>
<input type="text" id="username"/>
</div>
)
}
}
5. 事件处理
5.1、绑定事件
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写onclick , React里的事件是驼峰 onClick ,React的事件并不是原生事件,而是合成事件。
import React, { Component } from 'react'
export default class App extends Component {
a = 100
render() {
return (
<div>
<input />
<button onClick={ ()=>{
console.log("click1","如果处理逻辑过多, 不推荐这种写法")
} }>add1</button>
<button onClick={ this.handleClick2 }>add2</button>
<button onClick={ this.handleClick3 }>add3</button>
<button onClick={ ()=>{
this.handleClick4() // 比较推荐
} }>add4</button>
</div>
)
}
handleClick2(){
console.log("click2")
}
handleClick3 = ()=>{
console.log("click3")
}
handleClick4 = ()=>{
console.log("click4")
}
}
5.2、事件handler的写法
- 直接在render里写行内的箭头函数(不推荐)
- 在组件内使用箭头函数定义一个方法(推荐)
- 直接在组件内定义一个非箭头函数的方法,然后在render里直接使用 onClick={this.handleClick.bind(this)} (不推荐)
- 直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(推荐)
import React, { Component } from 'react'
export default class App extends Component {
a = 100
render() {
return (
<div>
<input />
<button onClick={ ()=>{
console.log("click1","如果处理逻辑过多, 不推荐这种写法",this.a)
} }>add1</button>
<button onClick={ this.handleClick2.bind(this) }>add2-不推荐这种写法</button>
<button onClick={ this.handleClick3 }>add3-推荐</button>
<button onClick={ ()=>this.handleClick4() }>add4-比较推荐-传参</button>
</div>
)
}
handleClick2(){
console.log("click2",this.a)
}
handleClick3 = (evt)=>{
console.log("click3",this.a,evt.target)
}
handleClick4(){
console.log("click4",this.a)
}
}
/*
React并不会真正的绑定事件到每一个具体《》的元素上,而是采用事件代理的模式:
*/
/*
call, 改变this, 自动执行函数
apply,改变this, 自动执行函数
bind, 改变this, 不会自动执行函数,手动加括号执行函数
*/
var obj1 = {
name:"obj1",
getName(){
console.log(this.name)
}
}
var obj2 = {
name:"obj2",
getName(){
console.log(this.name)
}
}
obj1.getName.bind(obj2)()
5.3、Event 对象
和普通浏览器一样,事件handler会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有 event.stopPropagation 、 event.preventDefault 这种常用的方法
6.Ref的应用
6.1给标签设置ref=“username”
通过这个获取this.refs.username , ref可以获取到应用的真实dom
6.2 给组件设置ref=“username”
通过这个获取this.refs.username ,ref可以获取到组件对象
6.3 新的写法
myRef = React.createRef()
<div ref={this.myRef}>hello</div>
访问this.myRef.current
import React, { Component } from 'react'
export default class App extends Component {
a = 100
myref = React.createRef()
render() {
return (
<div>
{/* <input ref="mytext"/> */}
<input ref={this.myref}/>
<button onClick={ ()=>{
// console.log("click1",this.refs.mytext.value)
console.log("click",this.myref.current.value)
} }>add1</button>
<button onClick={this.handleClick2.bind(this)}>add2</button>
</div>
)
}
handleClick2 = ()=>{
console.log("click2",this.myref.current.value)
}
}
五、组件的数据挂载方式
1、状态(state)
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
(1) 定义state
第一种方式
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
state = { name: 'React', isLiked: false }
render() {
return (
<div>
<h1>欢迎来到{this.state.name}的世界</h1>
<button> {this.state.isLiked ? '❤取消' : '🖤收藏'} </button>
</div>
)
}
} ReactDOM.render(<App />, document.getElementById('root'))
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
a = 1
state = {
mytext:"收藏",
myShow:true
}
render() {
// var text="收藏"
return (
<div>
<h1>欢迎来到react开发</h1>
<button onClick={()=>{
// this.state.mytext = "取消" //不用直接修改状态
this.setState({
// mytext:"取消收藏"
myShow:!this.state.myShow
}) // 间接修改状态
if(this.state.myShow){
console.log("收藏的逻辑")
}else{
console.log("取消收藏的逻辑")
}
}}>{this.state.myShow?'收藏':"取消收藏"}</button>
</div>
)
}
}
另一种方式
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = { name: 'React', isLiked: false }
}
render() {
return (
<div> <h1>欢迎来到{this.state.name}的世界</h1>
<button> {this.state.isLiked ? '❤取消' : '🖤收藏'} </button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
import React, { Component } from 'react'
export default class App extends Component {
a = 1
// state = {
// mytext:"收藏",
// myShow:true
// }
constructor(){
super()
this.state = {
mytext:"收藏",
myShow:true,
myname:"kerwin"
}
}
render() {
// var text="收藏"
return (
<div>
<h1>欢迎来到react开发-{this.state.myname}</h1>
<button onClick={()=>{
// this.state.mytext = "取消" //不用直接修改状态
this.setState({
// mytext:"取消收藏"
myShow:!this.state.myShow,
myname:"xiaoming"
}) // 间接修改状态
if(this.state.myShow){
console.log("收藏的逻辑")
}else{
console.log("取消收藏的逻辑")
}
}}>{this.state.myShow?'收藏':"取消收藏"}</button>
</div>
)
}
}
this.state 是纯js对象,在vue中,data属性是利用 Object.defineProperty 处理过的,更改data的数据的时候会触发数据的 getter 和 setter ,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法 setState 。
(2) setState
isLiked 存放在实例的 state 对象当中,组件的 render 函数内,会根据组件的 state 的中的isLiked 不同显示“取消”或“收藏”内容。下面给 button 加上了点击的事件监听。
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class App extends Component {
constructor() {
super()
this.state = { name: 'React', isLiked: false }
}
handleBtnClick = () => { this.setState({ isLiked: !this.state.isLiked }) }
render() {
return (
<div> <h1>欢迎来到{this.state.name}的世界</h1>
<button onClick={this.handleBtnClick}> {this.state.isLiked ? '❤取消' : '🖤收藏'} </button>
</div>
)
}
} ReactDOM.render(<App />, document.getElementById('root'))
setState 有两个参数
第一个参数可以是对象,也可以是方法return一个对象,我们把这个参数叫做 updater
- 参数是对象
this.setState({ isLiked: !this.state.isLiked })
- 参数是方法
this.setState((prevState, props) => { return { isLiked: !prevState.isLiked } })
注意的是这个方法接收两个参数,第一个是上一次的state, 第二个是props
setState 是异步的,所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState((prevState, props) => {
return {
isLiked: !prevState.isLiked }
}, () => {
console.log('回调里的',this.state.isLiked)
})
console.log('setState外部的',this.state.isLiked)
09-循环渲染.js
import React, { Component } from 'react'
export default class App extends Component {
state = {
list:[{
id:1,
text:"1111"
},
{
id:2,
text:"2222"
},
{
id:3,
text:"3333"
}]
}
render() {
// var newlist = this.state.list.map(item=><li>{item}</li>)
return (
<div>
<ul>
{
this.state.list.map( (item,index)=>
<li key={index}>{item.text}--{index}</li>
)
}
</ul>
</div>
)
}
}
/*
为了列表的复用和重排, 设置key值, 提高性能
理想key ,item.id
不涉及到列表的增加删除 ,重排, 设置成索引没有问题。
*/
/*
原生js - map
*/
var list= ["aa","bb","cc"]
var newlist = list.map(item=>`<li>${item}</li>`)
console.log(newlist.join(""))
10-todolist.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
a = 100
myref = React.createRef()
state = {
list:[{
id:1,
mytext:"aaa"
},
{
id:2,
mytext:"bbb"
},
{
id:3,
mytext:"ccc"
}]
}
render() {
return (
<div>
{/* <input ref="mytext"/> */}
<input ref={this.myref}/>
<button onClick={this.handleClick2}>add2</button>
<ul>
{
this.state.list.map( (item,index)=>
<li key={item.id}>
{item.mytext}
{/* <button onClick={this.handleDelClick.bind(this,index)}>del</button> */}
<button onClick={()=>this.handleDelClick(index)}>del</button>
</li>
)
}
</ul>
</div>
)
}
handleClick2 = ()=>{
console.log("click2",this.myref.current.value)
// this.setState
// 不要直接修改状态, 可能会造成不可预期的问题。
// this.state.list.push(this.myref.current.value)
let newlist = [...this.state.list]
newlist.push({
id:Math.random()*100000000, //生成不同id的函数
mytext:this.myref.current.value
})
// console.log()
this.setState({
list:newlist
})
}
handleDelClick(index){
console.log("del-click",index)
// 不要直接修改状态, 可能会造成不可预期的问题。
let newlist = this.state.list.concat()
newlist.splice(index,1)
this.setState({
list:newlist
})
}
}
条件渲染
<div className={this.state.list.length===0?'':'hidden'}>暂无待办事项</div>
13-setState同步异步.js
import React, { Component } from 'react'
export default class App extends Component {
state ={
count:1
}
render() {
return (
<div>
{this.state.count}
<button onClick={this.handleAdd1}>add1</button>
<button onClick={this.handleAdd2}>add2</button>
</div>
)
}
handleAdd1 = ()=>{
this.setState({
count:this.state.count+1
},()=>{
console.log(this.state.count)
})
this.setState({
count:this.state.count+1
},()=>{
console.log(this.state.count)
})
//setState处在同步的逻辑中, 异步更新状态,更新真实dom
this.setState({
count:this.state.count+1
},()=>{
console.log(this.state.count)
// 状态 和真实dom已经更新完了,
})
}
handleAdd2 = ()=>{
//setState处在异步的逻辑中, 同步更新状态,同步更新真实dom
setTimeout(()=>{
this.setState({
count:this.state.count+1
})
console.log(this.state.count)
this.setState({
count:this.state.count+1
})
console.log(this.state.count)
this.setState({
count:this.state.count+1
})
console.log(this.state.count)
},0)
}
}
/*
setState处在同步的逻辑中, 异步更新状态,更新真实dom
setState处在异步的逻辑中, 同步更新状态,同步更新真实dom,
setState 接受第二个参数, 第二个参数式回调函数, 状态和dom更新完后就会被触发。
*/
2、属性(props)
props 是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props属性是描述性质、特点的,组件自己不能随意更改。
之前的组件代码里面有 props 的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收 props :
(1) 在组件上通过key=value 写属性,通过this.props获取属性,这样组件的可复用性提高了。
(2) 注意在传参数时候,如果写成isShow=“true” 那么这是一个字符串 如果写成isShow={true} 这个 是布尔值
(3) {…对象} 展开赋值
(4) 默认属性值
*.defaultProps = { }
static defaultProps = {
myname:"默认的myname",
myshow:true
}
(5) prop-types 属性验证
import propTypes from "prop-types";
*.propTypes={
name:propTypes.string,
age:propTypes.number
}
static propTypes={
myname:propTypes.string,
myshow:propTypes.bool
}
2.1属性初识
父亲
import React, { Component } from 'react'
import Navbar from './Navbar'
export default class App extends Component {
render() {
return (
<div>
<div>
<h2>首页</h2>
<Navbar title="首页" leftshow={false}/>
</div>
<div>
<h2>列表</h2>
<Navbar title="列表" />
</div>
<div>
<h2>购物车</h2>
<Navbar title="购物车" />
</div>
</div>
)
}
}
孩子
\Navbar\index.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class Navbar extends Component {
state = {
//只能内部自己用的,外面无法改变。
}
//属性是父组件传来的,this.props
render() {
// console.log(this.props)
let {title,leftshow} = this.props
console.log(this.props)
// 接受属性 做验证?????
return (
<div>
{leftshow && <button>返回</button>}
navbar-{title}
<button>home</button>
</div>
)
}
}
2.2属性验证
import React, { Component } from 'react'
import Navbar from './Navbar'
export default class App extends Component {
render() {
return (
<div>
<div>
<h2>首页</h2>
<Navbar title="首页" leftshow={false}/>
</div>
<div>
<h2>列表</h2>
<Navbar title="列表" />
</div>
<div>
<h2>购物车</h2>
<Navbar title="购物车" />
</div>
</div>
)
}
}
子组件
import React, { Component } from 'react'
import kerwinPropTypes from 'prop-types'
console.log(kerwinPropTypes)
export default class Navbar extends Component {
state = {
//只能内部自己用的,外面无法改变。
}
// 类属性
static propTypes = {
title:kerwinPropTypes.string,
leftshow:kerwinPropTypes.bool
}
a = 100
//属性是父组件传来的,this.props
render() {
// console.log(this.props)
let {title,leftshow} = this.props
console.log(this.props)
// 接受属性 做验证?????
return (
<div>
{leftshow && <button>返回</button>}
navbar-{title}
<button>home</button>
</div>
)
}
}
class Test {
a =1 //对象属性
static a =100 // 类属性 (静态属性)
}
// Test.a = 100
var obj = new Test();
console.log(Test.a,obj.a)
2.3默认属性
父组件
import React, { Component } from 'react'
import Navbar from './Navbar'
export default class App extends Component {
render() {
return (
<div>
<div>
<h2>首页</h2>
<Navbar title="首页" leftshow={false}/>
</div>
<div>
<h2>列表</h2>
<Navbar title="列表" />
</div>
<div>
<h2>购物车</h2>
<Navbar title="购物车" />
</div>
</div>
)
}
}
子组件
import React, { Component } from 'react'
import kerwinPropTypes from 'prop-types'
console.log(kerwinPropTypes)
export default class Navbar extends Component {
state = {
//只能内部自己用的,外面无法改变。
}
// 类属性
static propTypes = {
title:kerwinPropTypes.string,
leftshow:kerwinPropTypes.bool
}
//默认值
static defaultProps = {
leftshow:true
}
a = 100
//属性是父组件传来的,this.props
render() {
// console.log(this.props)
let {title,leftshow} = this.props
console.log(this.props)
// 接受属性 做验证?????
return (
<div>
{leftshow && <button>返回</button>}
navbar-{title}
<button>home</button>
</div>
)
}
}
class Test {
a =1 //对象属性
static a =100 // 类属性
}
// Test.a = 100
var obj = new Test();
console.log(Test.a,obj.a)
子组件(增加种传参方式)
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import Navbar from './Navbar'
export default class App extends Component {
render() {
// 上面父组件传来的一个对象
var obj = {
title:"测试",
leftshow:false
}
return (
<div>
<div>
<h2>首页</h2>
<Navbar title="首页" leftshow={false}/>
</div>
<div>
<h2>列表</h2>
<Navbar title="列表" />
</div>
<div>
<h2>购物车</h2>
<Navbar title="购物车" />
</div>
<Navbar title={obj.title} leftshow={obj.leftshow} />
<Navbar {...obj} />
</div>
)
}
}
函数式组件
父组件
import React, { Component } from 'react'
import Navbar from './Navbar'
import Sidebar from './Sidebar'
export default class App extends Component {
render() {
return (
<div>
{/* 类组件 */}
<Navbar title="导航"></Navbar>
{/* 函数式组件 */}
<Sidebar bg="red" position="left"></Sidebar>
</div>
)
}
}
子组件
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React from 'react'
export default function Sidebar(props) {
// console.log(this)
let {bg,position} = props
console.log(position)
var obj1 = {
left:0
}
var obj2 = {
right:0
}
var obj = {
background:bg,
width:"200px",
position:"fixed"
}
var styleobj = position==="left"?{...obj,...obj1}:{...obj,...obj2}
return (
<div style={styleobj}>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
)
}
// Sidebar.defaultProps
// Sidebar.propTypes
3、属性vs状态
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
- 属性能从父组件获取,状态不能
- 属性可以由父组件修改,状态不能
- 属性能在内部设置默认值,状态也可以,设置方式不一样
- 属性不在组件内部修改,状态要在组件内部修改
- 属性能设置子组件初始值,状态不可以
- 属性可以修改子组件的值,状态不可以
state 的主要作用是用于组件保存、控制、修改自己的可变状态。 state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。 state 中状态可以通过 this.setState 方法进行更新, setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props ,否则组件的 props 永远保持不变。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
4、渲染数据
- 条件渲染
{ condition ? '渲染列表的代码' : '空空如也' }
- 列表渲染
// 数据 const people = [{ id: 1, name: 'Leo', age: 35 }, {id: 2, name: 'XiaoMing', age: 16}]
// 渲染列表
{
people.map(person => {
return (
<dl key={person.id}>
<dt>{person.name}</dt>
<dd>age: {person.age}</dd>
</dl>
)
})
}
React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是 key ,这个 key 必须是每个元素唯一的标识
- dangerouslySetInnerHTML
对于富文本创建的内容,后台拿到的数据是这样的:
content = "<p>React.js是一个构建UI的库</p>"
处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用 dangerouslySetHTML 属性,它允许我们动态设置 innerHTML
class App extends Component {
constructor() {
super()
this.state = { content: "<p>React.js是一个构建UI的库</p>" }
} render() {
return (
// 注意这里是两个下下划线 __html
<div
dangerouslySetInnerHTML={{ __html: this.state.content }} />)
}
} ReactDOM.render(<App />, document.getElementById('root'))
六、表单中的受控组件与非受控组件
1、非受控组件
React要编写一个非受控组件,可以 使用 ref 来从 DOM 节点中获取表单数据,就是非受控组件。
例如,下面的代码使用非受控组件接受一个表单的值:
因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
默认值
在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个defaultValue 属性,而不是 value 。
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
myusername = React.createRef()
render() {
return (
<div>
<h1>登录页</h1>
<input type="text" ref={this.myusername} defaultValue="kerwin"/>
<button onClick={()=>{
console.log(this.myusername.current.value)
}}>登录</button>
<button onClick={()=>{
this.myusername.current.value = ""
}}>重置</button>
{/* <Child myvalue={this.myusername.current.value}/> */}
</div>
)
}
}
2、受控组件
由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value ,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。
对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他
事件处理函数重置,但这意味着你需要编写更多的代码。
注意: 另一种说法(广义范围的说法),React组件的数据渲染是否被调用者传递的 props 完全控制,控制则为受控组件,否则非受控组件
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
// myusername = React.createRef()
state = {
username:"kerwin"
}
render() {
return (
<div>
<h1>登录页</h1>
<input type="text" value={this.state.username} onChange={(evt)=>{
console.log("onChange",evt.target.value)
this.setState({
username:evt.target.value
})
}}/>
<button onClick={()=>{
console.log(this.state.username)
}}>登录</button>
<button onClick={()=>{
this.setState({
username:""
})
}}>重置</button>
{/* <Child myvalue={this.state.username}/> */}
</div>
)
}
}
七. 组件通信的方式
1. 父子组件通信方式
(1) 传递数据(父传子)与传递方法(子传父)
(2) ref标记 (父组件拿到子组件的引用,从而调用子组件的方法)
在父组件中清除子组件的input输入框的value值。this.refs.form.reset()
037-父子通信版-表单域组件
import React, { Component } from 'react'
class Navbar extends Component{
render(){
return <div style={{background:"red"}}>
<button onClick={()=>{
console.log("子通知父, 让父的isSHow 取反。",this.props.event)
this.props.event() //调用父组件传来啊的回调函数
}}>click</button>
<span>navbar</span>
</div>
}
}
class Sidebar extends Component{
render(){
return <div style={{background:"yellow",width:"200px"}}>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
}
}
export default class App extends Component {
state = {
isShow:false
}
handleEvent = ()=>{
this.setState({
isShow:!this.state.isShow
})
// console.log("父组件定义的event事件")
}
render() {
return (
<div>
<Navbar event={this.handleEvent}/>
{/* <button onClick={()=>{
this.setState({
isShow:!this.state.isShow
})
}}>click</button> */}
{this.state.isShow && <Sidebar/>}
</div>
)
}
}
/*
父传子 :属性,
子传父: 回调函数 callback
*/
04-父子通信版-表单域组件.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Field extends Component{
render(){
return <div style={{background:"yellow"}}>
<label>{this.props.label}</label>
<input type={this.props.type} onChange={(evt)=>{
// console.log(evt.target.value)
this.props.onChangeEvent(evt.target.value)
}} value={this.props.value}/>
</div>
}
}
export default class App extends Component {
state = {
username:localStorage.getItem("username"),
password:""
}
render() {
return (
<div>
<h1>登录页面</h1>
<Field label="用户名" type="text" onChangeEvent={(value)=>{
// console.log(value)
this.setState({
username:value
})
}} value={this.state.username}/>
<Field label="密码" type="password" onChangeEvent={(value)=>{
this.setState({
password:value
})
}} value={this.state.password}/>
<button onClick={()=>{
console.log(this.state.username,this.state.password,'发送后端验证')
}}>登录</button>
<button onClick={()=>{
this.setState({
username:"",
password:""
})
}}>取消</button>
</div>
)
}
}
038-ref版-表单域组件
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Field extends Component{
state = {
value:""
}
clear(){
this.setState({
value:""
})
}
setValue(value){
this.setState({
value:value
})
}
render(){
return <div style={{background:"yellow"}}>
<label>{this.props.label}</label>
<input type={this.props.type} onChange={(evt)=>{
this.setState({
value:evt.target.value
})
}} value={this.state.value}/>
</div>
}
}
export default class App extends Component {
username = React.createRef()
password = React.createRef()
render() {
return (
<div>
<h1>登录页面</h1>
<Field label="用户名" type="text" ref={this.username}/>
<Field label="密码" type="password" ref={this.password}/>
<button onClick={()=>{
console.log(this.username.current.state.value,
this.password.current.state.value
)
}}>登录</button>
<button onClick={()=>{
this.username.current.clear()
this.password.current.clear()
}}>取消</button>
</div>
)
}
}
2. 非父子组件通信方式
(1) 状态提升(中间人模式)
React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件上.在父组件上改变这个状态然后通过props分发给子组件.
(2) 发布订阅模式实现
(3) context状态树传参
(1) 状态提升(中间人模式) 06-中间人模式.js
import React, { Component } from 'react'
import axios from 'axios'
import './css/03-communination.css'
export default class App extends Component {
constructor(){
super()
this.state = {
filmList:[],
info:""
}
axios.get(`/test.json`).then(res=>{
console.log(res.data.data.films)
this.setState({
filmList:res.data.data.films
})
})
}
render() {
return (
<div>
{/* {this.state.info} */}
{
this.state.filmList.map(item=>
<FilmItem key={item.filmId} {...item} onEvent={(value)=>{
// console.log("父组件接受",value)
this.setState({
info:value
})
}}></FilmItem>
)
}
<FilmDetail info={this.state.info}></FilmDetail>
</div>
)
}
}
/*受控组件*/
class FilmItem extends Component{
render(){
// console.log(this.props)
let {name, poster,grade,synopsis} = this.props
return <div className="filmitem" onClick={()=>{
// console.log(synopsis)
this.props.onEvent(synopsis)
}}>
<img src={poster} alt={name}/>
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
}
}
class FilmDetail extends Component{
render(){
return <div className="filmdetail">
{this.props.info}
</div>
}
}
(2) 发布订阅模式实现 08-订阅发布模式案例.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import axios from 'axios'
import './css/03-communination.css'
//调度中心
var bus = {
list:[],
//订阅
subscribe(callback){
// console.log(callback)
this.list.push(callback)
},
//发布
publish(text){
//遍历所有的list, 将回调函数执行
// console.log(this.list)
this.list.forEach(callback=>{
callback && callback(text)
})
}
}
export default class App extends Component {
constructor(){
super()
this.state = {
filmList:[],
}
axios.get(`/test.json`).then(res=>{
console.log(res.data.data.films)
this.setState({
filmList:res.data.data.films
})
})
}
render() {
return (
<div>
{/* {this.state.info} */}
{
this.state.filmList.map(item=>
<FilmItem key={item.filmId} {...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
)
}
}
/*受控组件*/
class FilmItem extends Component{
render(){
// console.log(this.props)
let {name, poster,grade,synopsis} = this.props
return <div className="filmitem" onClick={()=>{
// console.log(synopsis)
bus.publish(synopsis)
}}>
<img src={poster} alt={name}/>
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
}
}
class FilmDetail extends Component{
constructor(){
super()
this.state = {
info:""
}
bus.subscribe((info)=>{
console.log("我再filmDetail中定义",info)
this.setState({
info:info
})
})
}
render(){
return <div className="filmdetail">
{this.state.info}
</div>
}
}
(3) context状态树传参 09-context.js
//a. 先定义全局context对象
import React from 'react'
const GlobalContext = React.createContext()
export default GlobalContext
//b. 根组件引入GlobalContext,并使用GlobalContext.Provider(生产者)
//重新包装根组件 class App {}
<GlobalContext.Provider
value={{ name:"kerwin", age:100, content:this.state.content, show:this.show.bind(this), hide:this.hide.bind(this) }}
>
<之前的根组件></之前的根组件>
</GlobalContext.Provider>
//c. 任意组件引入GlobalContext并调用context,使用GlobalContext.Consumer(消费者)
<GlobalContext.Consumer>
{ context => { this.myshow = context.show; //可以在当前组件任意函数触发
this.myhide = context.hide; //可以在当前组件任意函数触发 return (
<div>{context.name}-{context.age}-{context.content}</div>
) } }
</GlobalContext.Consumer>
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import axios from 'axios'
import './css/03-communination.css'
const GlobalContext = React.createContext() //创建context对象
export default class App extends Component {
constructor(){
super()
this.state = {
filmList:[],
info:""
}
axios.get(`/test.json`).then(res=>{
console.log(res.data.data.films)
this.setState({
filmList:res.data.data.films
})
})
}
render() {
return (
<GlobalContext.Provider value={{
call:"打电话",
sms:"短信",
info:this.state.info,
changeInfo:(value)=>{
this.setState( {
info:value
})
}
}}>
<div>
{/* {this.state.info} */}
{
this.state.filmList.map(item=>
<FilmItem key={item.filmId} {...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
</GlobalContext.Provider>
)
}
}
/*受控组件*/
class FilmItem extends Component{
render(){
// console.log(this.props)
let {name, poster,grade,synopsis} = this.props
return (
<GlobalContext.Consumer>
{
(value)=>{
console.log(value)
return <div className="filmitem" onClick={()=>{
console.log(synopsis)
// this.props.onEvent(synopsis)
// value.info = "2222222"
// console.log(value)
value.changeInfo(synopsis)
}}>
<img src={poster} alt={name}/>
<h4>{name}</h4>
<div>观众评分:{grade}</div>
</div>
}
}
</GlobalContext.Consumer>
)
}
}
class FilmDetail extends Component{
render(){
return (
<GlobalContext.Consumer>
{
(value)=><div className="filmdetail">
detail-{value.info}
</div>
}
</GlobalContext.Consumer>
)
}
}
上面所有的一个思考:state,属性传递,受控组件,非受控组件,组件通信,这些基础的概念,需要我在设计组件的时候,充分的思考,自己的组件属于,那些类型。不是拿到手就开始写
注意:GlobalContext.Consumer内必须是回调函数,通过context方法改变根组件状态
context优缺点:
优点:跨组件访问数据
缺点:react组件树种某个上级组件shouldComponetUpdate 返回false,当context更新时,不会引起下级组件更新
八. 插槽
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Child extends Component{
render(){
return <div>
child
{/* 插槽 vue slot,具名插槽 */}
{this.props.children[2]}
{this.props.children[1]}
{this.props.children[0]}
</div>
}
}
class Swiper extends Component{
render(){
return <div>
{this.props.children}
</div>
}
}
export default class App extends Component {
render() {
return (
<div>
<Swiper>
<div>111111</div>
<div>222222</div>
<div>333333</div>
</Swiper>
<Swiper>
{/* <div><img/>111111</div>
<div><img/>222222</div>
<div><img/>333333</div> */}
</Swiper>
<Swiper>
{/* <img/>
<img/>
<img/> */}
</Swiper>
<Child>
<div>11111111</div>
<div>22222222</div>
<div>33333333</div>
{
// children
}
</Child>
</div>
)
}
}
/*
1. 为了复用
2. 一定程度减少父子通信。
*/
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Navbar extends Component{
render(){
return <div style={{background:"red"}}>
{this.props.children}
<span>navbar</span>
</div>
}
}
class Sidebar extends Component{
render(){
return <div style={{background:"yellow",width:"200px"}}>
<ul>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
<li>11111</li>
</ul>
</div>
}
}
export default class App extends Component {
state = {
isShow:false
}
render() {
return (
<div>
<Navbar>
<button onClick={()=>{
this.setState({
isShow:!this.state.isShow
})
}}>click</button>
</Navbar>
{this.state.isShow && <Sidebar/>}
</div>
)
}
}
/*
父传子 :属性,
子传父: 回调函数 callback
*/
九. React生命周期
- 初始化阶段

12-生命周期-初始化.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
myname:"kerwin"
}
componentWillMount(){
console.log("第一次will mount",this.state.myname,document.getElementById("myname"))
// 第一次上树前的 最后一次修改状态机会
this.setState({
myname:"Kerwin"
})
//初始化数据的作用。
}
componentDidMount(){
console.log("第一次did mount",document.getElementById("myname"))
// 数据请求axios
// 订阅函数调用
// setInterval
// 基于创建的完的dom进行 初始化,,,,,,BetterScroll
}
render() {
console.log("render")
return (
<div>
<span id="myname">{this.state.myname}</span>
</div>
)
}
}
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
myname:"kerwin"
}
UNSAFE_componentWillMount(){
console.log("第一次will mount",this.state.myname,document.getElementById("myname"))
// 第一次上树前的 最后一次修改状态机会
this.setState({
myname:"Kerwin"
})
//初始化数据的作用。
}
componentDidMount(){
console.log("第一次did mount",document.getElementById("myname"))
// 数据请求axios
// 订阅函数调用
// setInterval
// 基于创建的完的dom进行 初始化,,,,,,BetterScroll
}
render() {
console.log("render")
return (
<div>
<span id="myname">{this.state.myname}</span>
</div>
)
}
}
- 运行中阶段

14-生命周期-更新阶段1.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import axios from 'axios'
import BetterScroll from 'better-scroll'
export default class App extends Component {
state = {
myname:"kerwin",
list:[]
}
componentDidMount(){
axios.get("/test.json").then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
// 访问
})
}
render() {
console.log("render")
return (
<div>
<button onClick={()=>{
this.setState({
myname:"tiechui"
})
}}>click</button>
<span id="myname">{this.state.myname}</span>
<div id="warpper" style={{
height:"100px",overflow:"hidden",background:"yellow"
}}>
<ul>
{
this.state.list.map(item=>
<li key={item.filmId}>{item.name}</li>
)
}
</ul>
</div>
</div>
)
}
UNSAFE_componentWillUpdate(){
console.log("componentWillUpdate",document.getElementById("myname").innerHTML)
}
componentDidUpdate(prevProps,prevState){
console.log("componentDidUpdate",document.getElementById("myname").innerHTML)
// 更新后, 想要获取dom节点, 更新
console.log(prevState.list)
if(prevState.list.length===0){
new BetterScroll("#warpper")
}
}
}
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
myname:"kerwin"
}
render() {
console.log("render")
return (
<div>
<button onClick={()=>{
// this.state.myname = "xiaoming"
this.setState({
myname:"xiaoming"
})
}}>click</button>
{this.state.myname}
</div>
)
}
// scu 性能优化函数
shouldComponentUpdate(nextProps,nextState){
// return true; //应该更新
//return false;; //阻止更新
// this.state 老的状态
// nextState 新的状态
if(JSON.stringify(this.state)!== JSON.stringify(nextState)){
return true
}
return false
}
UNSAFE_componentWillUpdate(){
console.log("UNSAFE_componentWillUpdate")
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
}
17-生命周期-更新阶段3.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
class Child extends Component{
state ={
title:""
}
render(){
return <div>child-{this.state.title}</div>
}
componentWillReceiveProps(nextProps){
console.log("componentWillReceiveProps",nextProps)
// 最先获得父组件传来的属性, 可以利用属性进行ajax或者逻辑处理。
// 把属性转化成孩子自己的状态。
this.setState({
title:nextProps.text+"kerwin"
})
}
}
export default class App extends Component {
state = {
text:"11111111111"
}
render() {
return (
<div>
{
this.state.text
}
<button onClick={()=>{
this.setState({
text:"222222222222"
})
}}>click</button>
<Child text={this.state.text}/>
</div>
)
}
}
- 销毁阶段

19-生命周期-销毁.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
isCreated:true
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
isCreated:!this.state.isCreated
})
}}>click</button>
{/* {this.state.isCreated?<Child/>:""} */}
{this.state.isCreated && <Child/>}
</div>
)
}
}
class Child extends Component{
render(){
return <div>
child
</div>
}
componentDidMount() {
window.onresize = ()=>{
console.log("resize")
}
this.timer = setInterval(()=>{
console.log("111")
},1000)
}
componentWillUnmount(){
console.log("componentWillUnmount")
window.onresize = null
clearInterval(this.timer)
}
}
老生命周期的问题
(1) componentWillMount ,在ssr中 这个方法将会被多次调用, 所以会重复触发多遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏 , 变得不够安全高效逐步废弃。
(2) componentWillReceiveProps 外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求
(3) componetWillupdate, 更新前记录 DOM 状态, 可能会做一些处理,与componentDidUpdate相隔时间如果过长, 会导致状态不太信
新生命周期的替代
(1)getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子) ,返回一个对象作为新的state,返回null则说明不需要在这里更新state
(2) getSnapshotBeforeUpdate 取代了 componetWillUpdate ,触发时间为update发生的时候,在render之后,dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。
react中性能优化的方案
- shouldComponentUpdate
控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。
- PureComponent
PureComponent会帮你 比较新props 跟 旧的props, 新的state和老的state(值相等,或者
对象含有相同的属性、且属性值相等 ),决定shouldcomponentUpdate 返回true 或者
false, 从而决定要不要呼叫 render function。
注意:
如果你的 state 或 props 『永远都会变』,那 PureComponent 并不会比较快,因为
shallowEqual 也需要花时间。
20-新生命周期-1.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
myname:"kerwin",
myage:100
}
// componentWillMount 初始化
static getDerivedStateFromProps(nextProps,nextState){
// console.log(this)
console.log("getDrivedStateFromProps",nextState)
return {
myname:nextState.myname.substring(0,1).toUpperCase()+nextState.myname.substring(1)
}
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
myname:"xiaoming"
})
}}>click</button>
app - {this.state.myname}-{this.state.myage}
</div>
)
}
}
22-新生命周期-2.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
mytext:"111111"
}
render() {
console.log("render")
return (
<div>
app
<button onClick={()=>{
this.setState({
mytext:"222222"
})
}}>click</button>
{this.state.mytext}
</div>
)
}
// componentWillUpdate(){
// console.log("componentWillUpdate")
// }
componentDidUpdate(prevProps,prevState,value){
console.log("componentDidUpdate",value)
}
getSnapshotBeforeUpdate(){
console.log("getSnapshotBeforeUpdate")
return 100;
}
}
24-性能优化.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
state = {
myname:"kerwin"
}
render() {
console.log("render")
return (
<div>
<button onClick={()=>{
// this.state.myname = "xiaoming"
this.setState({
myname:"xiaoming"
})
}}>click</button>
{this.state.myname}
</div>
)
}
// scu 性能优化函数
// shouldComponentUpdate(nextProps,nextState){
// // return true; //应该更新
// //return false;; //阻止更新
// // this.state 老的状态
// // nextState 新的状态
// if(JSON.stringify(this.state)!== JSON.stringify(nextState)){
// return true
// }
// return false
// }
UNSAFE_componentWillUpdate(){
console.log("UNSAFE_componentWillUpdate")
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
}
20-新生命周期-1.js
052-新生命周期-getDerivedStateFromProps
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
myname:"kerwin",
myage:100
}
// componentWillMount 初始化
static getDerivedStateFromProps(nextProps,nextState){
// console.log(this)
console.log("getDrivedStateFromProps",nextState)
return {
myname:nextState.myname.substring(0,1).toUpperCase()+nextState.myname.substring(1)
}
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
myname:"xiaoming"
})
}}>click</button>
app - {this.state.myname}-{this.state.myage}
</div>
)
}
}
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
state = {
type:1
}
render() {
return (
<div>
<ul>
<li onClick={()=>{
this.setState({
type:1
})
}}>正在热映</li>
<li onClick={()=>{
this.setState({
type:2
})
}}>即将上映</li>
</ul>
<FilmList type={this.state.type}></FilmList>
</div>
)
}
}
class FilmList extends Component{
state = {
list:[],
type:1
}
//初始化-执行一次
componentDidMount() {
// console.log(this.props.type)
if(this.props.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
static getDerivedStateFromProps(nextProps,nextState){
// console.log(this)
console.log("getDrivedStateFromProps",nextProps)
return {
type:nextProps.type
}
}
componentDidUpdate(prevProps,prevState){
console.log(this.state.type)
if(this.state.type===prevState.type){
return
}
if(this.state.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
// UNSAFE_componentWillReceiveProps(nextProps){
// if(nextProps.type===1){
// //请求卖座正在热映的数据
// console.log("请求卖座正在热映的数据")
// axios({
// url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
// headers:{
// 'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
// 'X-Host': 'mall.film-ticket.film.list'
// }
// }).then(res=>{
// console.log(res.data.data.films)
// this.setState({
// list:res.data.data.films
// })
// })
// }else{
// //请求卖座即将上映的数据
// console.log("请求卖座即将上映的数据")
// axios({
// url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
// headers:{
// 'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
// 'X-Host': 'mall.film-ticket.film.list'
// }
// }).then(res=>{
// console.log(res.data.data.films)
// this.setState({
// list:res.data.data.films
// })
// })
// }
// }
render(){
return <ul>
{
this.state.list.map(item=>
<li key={item.filmId}>{item.name}</li>
)
}
</ul>
}
}
053-新生命周期-getSnapshotBeforeUpdate
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
export default class App extends Component {
state = {
mytext:"111111"
}
render() {
console.log("render")
return (
<div>
app
<button onClick={()=>{
this.setState({
mytext:"222222"
})
}}>click</button>
{this.state.mytext}
</div>
)
}
// componentWillUpdate(){
// console.log("componentWillUpdate")
// }
componentDidUpdate(prevProps,prevState,value){
console.log("componentDidUpdate",value)
}
getSnapshotBeforeUpdate(){
console.log("getSnapshotBeforeUpdate")
return 100;
}
}
23-新生命周期-2-案例.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import './css/04-clear.css'
export default class App extends Component {
state = {
list:[1,2,3,4,5,6,7,8,9]
}
myref = React.createRef()
getSnapshotBeforeUpdate(){
//获取容器高度
console.log(this.myref.current.scrollHeight)
return this.myref.current.scrollHeight
}
componentDidUpdate(prevProps,prevState,value){
console.log(this.myref.current.scrollHeight)
this.myref.current.scrollTop+=this.myref.current.scrollHeight-value
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
list:[...[11,12,13,14,15,16,17,18,19],...this.state.list]
})
}}>来邮件</button>
<h1>邮箱应用</h1>
<div style={{height:"200px",overflow:"auto"}} ref={this.myref}>
<ul>
{
this.state.list.map(item=>
<li key={item} style={{height:"100px",background:"yellow"}}>{item}</li>
)
}
</ul>
</div>
</div>
)
}
}
十. React Hooks
使用hooks理由
- 高阶组件为了复用,导致代码层级复杂
- 生命周期的复杂
- 写成functional组件,无状态组件 ,因为需要状态,又改成了class,成本高
1.1useState(保存组件状态)
const [state, setstate] = useState(initialState)
01-useState.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState} from 'react'
export default function App() {
const [name,setName] = useState("kerwin")
const [age, setage] = useState(100)
// console.log(obj)
return (
<div>
<button onClick={()=>{
// console.log(name)
setName("xiaoming")
setage(18)
}}>click</button>
app-{name}-{age}
</div>
)
}
02-todolist.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState} from 'react'
export default function App() {
const [text, settext] = useState("")
const [list,setList] = useState(["aa","bb","cc"])
const handleChange = (evt)=>{
// console.log("handleChange",evt.target.value)
settext(evt.target.value)
}
const handleAdd = ()=>{
console.log(text)
// list.push("")
setList([...list,text])
//情况
settext("")
}
const handleDel = (index)=>{
console.log(index)
var newlist = [...list]
newlist.splice(index,1)
setList(newlist)
}
return (
<div>
<input onChange={handleChange} value={text}/>
<button onClick={handleAdd}>add</button>
<ul>
{
list.map((item,index)=>
<li key={item}>
{item}
<button onClick={()=>handleDel(index)}>del</button>
</li>
)
}
</ul>
{!list.length && <div>暂无待办事项</div>}
</div>
)
}
1.2useEffect(处理副作用)和useLayoutEffect (同步执行副作用)
Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。
useEffect(() => {
//effect
return () => {
//cleanup
};
}, [依赖的状态;空数组,表示不依赖])
03-useEffect.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState,useEffect} from 'react'
import axios from 'axios'
export default function App() {
const [list, setlist] = useState([])
useEffect(()=>{
axios.get("/test.json").then(res=>{
console.log(res.data)
setlist(res.data.data.films)
})
},[]) // 传空数组,
return (
<div>
app
{
list.map(item=>
<li key={item.filmId}>{item.name}</li>
)
}
</div>
)
}
04-useEffect2.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState,useEffect} from 'react'
export default function App() {
const [name, setname] = useState("kerwin")
useEffect(() => {
setname(name.substring(0,1).toUpperCase()+name.substring(1))
}, [name])
// 第一次执行一次, 之后name(依赖)更新也会执行
return (
<div>
app-{name}
<button onClick={()=>{
setname("xiaoming")
}}>click</button>
</div>
)
}
06-useEffect3.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component,useLayoutEffect } from 'react'
export default class App extends Component {
state = {
isCreated:true
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
isCreated:!this.state.isCreated
})
}}>click</button>
{/* {this.state.isCreated?<Child/>:""} */}
{this.state.isCreated && <Child/>}
</div>
)
}
}
function Child (){
useLayoutEffect(() => {
window.onresize = ()=>{
console.log("resize")
}
var timer = setInterval(()=>{
console.log("111")
},1000)
return ()=>{
console.log('组件销毁')
window.onresize = null
clearInterval(timer)
}
}, [])
return <div>
child
</div>
}
useEffect和useLayoutEffect有什么区别?
简单来说就是调用时机不同, useLayoutEffect 和原来componentDidMount & componentDidUpdate 一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而 useEffect 是会在整个页面渲染完才会调用的代码。
官方建议优先使用 useEffect
However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
在实际使用时如果想避免页面抖动(在 useEffect 里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在 useLayoutEffect 里。在这里做点dom操作,这些dom修改会和 react 做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。
1.3useCallback(记忆函数)
防止因为组件重新渲染,导致方法被重新创建 ,起到缓存作用; 只有第二个参数 变化了,才重新声明一次
var handleClick = useCallback(()=>{
console.log(name)
},[name])
<button onClick={()=>handleClick()}>hello</button>
//只有name改变后, 这个函数才会重新声明一次,
//如果传入空数组, 那么就是第一次创建后就被缓存, 如果name后期改变了,拿到的还是老的name。
//如果不传第二个参数,每次都会重新声明一次,拿到的就是最新的name
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState,useCallback} from 'react'
export default function App() {
const [myname, setname] = useState("kerwin")
const [text, settext] = useState("")
const [list,setList] = useState(["aa","bb","cc"])
const handleChange = useCallback(
(evt)=>{
// console.log("handleChange",evt.target.value)
settext(evt.target.value)
},
[]
)
const handleAdd = useCallback(
()=>{
console.log(text)
// list.push("")
setList([...list,text])
//情况
settext("")
},
[text,list]
)
const handleDel = useCallback(
(index)=>{
console.log(index)
var newlist = [...list]
newlist.splice(index,1)
setList(newlist)
},
[list]
)
return (
<div>
{myname} - <button onClick={()=>{
setname("xiaomng")
}}>change-myname</button>
<input onChange={handleChange} value={text}/>
<button onClick={handleAdd}>add</button>
<ul>
{
list.map((item,index)=>
<li key={item}>
{item}
<button onClick={()=>handleDel(index)}>del</button>
</li>
)
}
</ul>
{!list.length && <div>暂无待办事项</div>}
</div>
)
}
1.4 useMemo 记忆组件
useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数 计算得到一个确定的值,比如记忆组件。
useRef(保存引用值)
const myswiper = useRef(null);
<Swiper ref={myswiper}/>
useReducer和useContext(减少组件层级)
09-useMemo.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { useState,useEffect,useMemo } from 'react'
import axios from 'axios'
export default function Cinema(){
const [mytext, setmytext] = useState("")
const [cinemaList, setcinemaList] = useState([])
useEffect(() => {
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
method:"get",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res=>{
setcinemaList(res.data.data.cinemas)
})
}, [])
const getCinemaList = useMemo(() => cinemaList.filter(item=>item.name.toUpperCase().includes(mytext.toUpperCase()) ||
item.address.toUpperCase().includes(mytext.toUpperCase())
), [cinemaList,mytext])
return <div>
{/* {this.state.mytext} */}
<input value={mytext} onChange={(evt)=>{
setmytext(evt.target.value)
}}/>
{
getCinemaList.map(item=>
<dl key={item.cinemaId}>
<dt>{item.name}</dt>
<dd>{item.address}</dd>
</dl>
)
}
</div>
}
10-useRef.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState,useRef} from 'react'
export default function App() {
const [list,setList] = useState(["aa","bb","cc"])
const mytext = useRef() // React.createRef()
const handleAdd = ()=>{
console.log(mytext.current.value)
// list.push("")
setList([...list,mytext.current.value])
//清空
// settext("")
mytext.current.value = ""
}
const handleDel = (index)=>{
console.log(index)
var newlist = [...list]
newlist.splice(index,1)
setList(newlist)
}
return (
<div>
<input ref={mytext}/>
<button onClick={handleAdd}>add</button>
<ul>
{
list.map((item,index)=>
<li key={item}>
{item}
<button onClick={()=>handleDel(index)}>del</button>
</li>
)
}
</ul>
{!list.length && <div>暂无待办事项</div>}
</div>
)
}
11-useRef-保存值.js
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React,{useState,useRef} from 'react'
export default function App() {
const [count, setcount] = useState(0)
//useState 记忆函数, 记住状态
var mycount = useRef(0)
// var handleClick = ()=>{
// }
return (
<div>
<button onClick={()=>{
setcount(count+1)
// mycount++
mycount.current++
}}>add</button>
{count}-{mycount.current}
</div>
)
}
1.5 自定义hooks
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。
必须以“use”开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则
更多推荐
所有评论(0)