08-React扩展
1. setState的2种写法
案例:
export default class Demo extends Component {state = {count: 0}add = () => {// 获取当前的值const { count } = this.state// 更新状态this.setState({ count: count + 1 })console.log(count);}render() {const { count } = this.statereturn (<div><h2>当前求和为:{count}</h2><button onClick={this.add}>点击+1</button></div>)}
}
可以看到
setState
更新视图是一个异步的动作,同步的输出事件只能获取更新前的状态。可知setState()
是立即执行同步的,但是它引起的后续动作(reac
t模版更新)是异步的,要等后续的进程执行完再更新视图。
setStata
函数可以传入两个参数,除了需要更新的state
状态对象,还可以传入一个回调函数,这个回调函数是一个异步函数
export default class Demo extends Component {state = {count: 0}add = () => {// 获取当前的值const { count } = this.state// 更新状态this.setState({ count: count + 1 },() => {console.log(this.state.count)})}render() {const { count } = this.statereturn (<div><h2>当前求和为:{count}</h2><button onClick={this.add}>点击+1</button></div>)}
}
1).对象式的setState
setState(stateChange, [callback])
-
stateChange
为状态改变对象(该对象可以体现出状态的更改)this.setState({ count: count + 1 },() => {})
-
callback
是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用this.setState({ count: count + 1 },() => {console.log(this.state.count) })
2).函数式的setState
setState(updater, [callback])
-
updater
为返回stateChange
对象的函数。this.setState(() => {return {count: this.state.count + 1} },() => {})
-
updater
可以接收到state
和props
。this.setState((state,props) => {console.log(state,props);return {count: state.count + 1} },() => {})
-
callback
是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。this.setState((state,props) => {return {count: state.count + 1} },() => {console.log(state.count) })
3).总结
-
对象式的setState是函数式的setState的简写方式(语法糖)
-
使用原则:
-
如果新状态不依赖于原状态 ===> 使用对象方式
this.setState({count:99})
-
如果新状态依赖于原状态 ===> 使用函数方式
this.setState(state => ({ count: state.count + 1 }))
-
如果需要在
setState()
执行后获取最新的状态数据,要在第二个callback
函数中读取
-
2. 路由组件的lazyLoad
在实际开发中,整个React应用会有许多的组件,一般情况下在运行React项目时,程序会将所有组件全部加载,这样还未用到的组件也被加载,这样会影响程序整体运行的速度
实验:
export default class Demo extends Component {render() {return (<div><div className="row"><div className="col-xs-offset-2 col-xs-8"><div className="pae-header"><h2>路由项目</h2></div></div></div><div className="row"><div className="col-xs-2 col-xs-offset-2"><div className="list-group"><NavLink className="list-group-item" to="/about">About</NavLink><NavLink className="list-group-item" to="/home">Home</NavLink></div></div><div className="col-xs-6"><div className="panel"><div className="panel-body"><Route path="/about" component={About} /><Route path="/home" component={Home} /></div></div></div></div></div>)}
}
可以看出当点击每个组件的链接Tab时,程序并没有发送加载资源请求,说明一开始运行程序的时候,所有组件已经全部请求完毕了,为了减少资源的浪费和提升程序的性能,需要用到路由的懒加载。
1).lazy函数
通过React的
lazy
函数配合import()
函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Home=lazy(() => import('./Home'))
const About=lazy(() => import('./About'))
2).<Suspense>组件
通过
<Suspense>
组件包裹需要懒加载的组件,配置Lazy
使用
export default class Demo extends Component {render() {return (<div><div className="row"><div className="col-xs-offset-2 col-xs-8"><div className="pae-header"><h2>路由项目</h2></div></div></div><div className="row"><div className="col-xs-2 col-xs-offset-2"><div className="list-group"><NavLink className="list-group-item" to="/about">About</NavLink><NavLink className="list-group-item" to="/home">Home</NavLink></div></div><div className="col-xs-6"><div className="panel"><div className="panel-body"><Suspense><Route path="/about" component={About} /><Route path="/home" component={Home} /></Suspense></div></div></div></div></div>)}
}
还可以通过
<Suspense>
指定在加载得到路由打包文件前显示一个自定义loading
界面,当网速慢或者其他原因导致组件加载请求慢时会有一个备用组件作为优先显示
<Suspense fallback={<h1>Loading.....</h1>}><Route path="/about" component={About} /><Route path="/home" component={Home} />
</Suspense>
3. Hooks
1). React Hook/Hooks是什么?
(1).
Hook
是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用state
以及其他的 React 特性
2). 三个常用的Hook
a. State Hook
-
State Hook
让函数组件也可以有state
状态, 并进行状态数据的读写操作//类式组件 class Demo extends Component {state = {count: 0}add = () => {this.setState(state => ({ count: state.count + 1 }))}render() {const { count } = this.statereturn (<div><h2>当前求和为:{count}</h2><button onClick={this.add}>点击+1</button></div>)} }
-
语法:
const [xxx, setXxx] = React.useState(initValue)
-
useState()
说明:
- 参数: 第一次初始化指定的值在内部作缓存
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
//函数式组件
function Demo() {const [count, setCount] = React.useState(0)function add() {setCount(count+1)}return (<div><h2>当前求和为:{count}</h2><button onClick={add}>点击+1</button></div>)
}
setXxx()
的2种写法:
-
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值 -
setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值function add() {// 第一种写法setCount(count+1)// 第二种写法setCount(count => count + 1)}
b. Effect Hook
-
Effect Hook
可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)// 类式组件写法 class Demo extends Component {state = {count: 0}unmount = () => {createRoot(document.getElementById('root')).unmount();}componentDidMount() {this.timer = setInterval(() => {this.setState(state => ({ count: state.count + 1 }))}, 1000)}componentWillUnmount() {clearInterval(this.timer)}render() {const { count } = this.statereturn (<div><button onClick={this.unmount}>点击卸载</button></div>)} }
-
React中的副作用操作:
- 发
ajax
请求数据获取 - 设置订阅 / 启动定时器
- 手动更改真实DOM
- 发
-
语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作return () => {} }, [stateValue])
//函数式组件 function Demo() {const [count, setCount] = React.useState(0)React.useEffect(() => {let timer = setInterval(() => {setCount(count => count + 1)}, 1000)return () => {clearInterval(timer)}}, [])function unmount() {createRoot(document.getElementById('root')).unmount();}return (<div><h2>当前求和为:{count}</h2><button onClick={unmount}>点击卸载</button> </div>) }
分析:
-
使用
React.useEffect()
时不写第二个参数,那么Effect Hook
会监测所有状态值的变化function Demo() {const [count, setCount] = React.useState(0)const [name, setName] = React.useState('Tom')React.useEffect(() => {console.log('@@');})function add() {setCount(count => count + 1)}function changeName() {setName('Jack')}return (<div><h2>当前求和为:{count}</h2><h2>当前的名字为{name}</h2> <button onClick={add}>点击+1</button><button onClick={changeName}>点我改名</button></div>) }
-
使用
React.useEffect()
时写一个空数组为参数,那么Effect Hook
不会监测任何状态值的变化,回调函数只会在第一次render()后执行React.useEffect(() => {console.log('@@'); },[])
-
使用
React.useEffect()
时写值为指定状态值的非空数组作为第二个参数,那么Effect Hook
只会监测指定状态值的变化React.useEffect(() => {console.log('@@'); },[name])
-
React.useEffect()
中的返回函数return
在组件卸载前执行,一般在此做一些收尾工作, 比如清除定时器/取消订阅等,注意点:每次执行这里的更新该函数也会去执行一遍
React.useEffect(() => {let timer = setInterval(() => {setCount(count => count + 1)}, 1000)return () => {clearInterval(timer)} },[])
-
-
可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
c. Ref Hook
-
Ref Hook
可以在函数组件中存储/查找组件内的标签或任意其它数据// 类式组件的写法 class Demo extends Component {myRef=React.createRef();show=() => {alert(this.myRef.current.value)}render() {return (<div><input ref={this.myRef} type="text" /><button onClick={this.show}>点击显示</button></div>)} }
-
语法:
const refContainer = useRef()
-
作用:保存标签对象,功能与
React.createRef()
一样// 函数式组件的写法 function Demo() {const myRef=React.useRef()function show() {alert(myRef.current.value)}return (<div><input ref={myRef} type="text" /><button onClick={show}>点击显示</button></div>) }
4. Fragment
使用:
<Fragment><Fragment>
<></>
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {render() {return (<Fragment><input type="text" /><input type="text" /></Fragment> )}
}
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {render() {return (<><input type="text" /><input type="text" /></>)}
}
作用
可以不用必须有一个真实的DOM根标签了
未使用
Fragment
标签:
使用
Fragment
标签:
5. Context
理解:
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用:
需求:将A组件状态中的数据传递给C组件,不通过B组件的二次传递
export default class A extends Component {state = {username: 'tom',age: 18}render() {const { username, age } = this.statereturn (<div className='parent'><h3>我是A组件</h3><h4>我的用户名是:{username}</h4><h4>我的年龄是:{age}</h4><B/></div>)}
}
class B extends Component {render() {return (<div className='child'><h3>我是B组件</h3><C/></div>)}
}
class C extends Component {render() {return (<div className='grand'><h3>我是C组件</h3><h4>我从A组件拿到的用户名是:???</h4><h4>我从A组件拿到的年龄时是:???</h4></div>)}
}
-
创建Context容器对象:
const XxxContext = React.createContext()
// 创建context对象 const UserNameContext = React.createContext()
-
渲染子组时,外面包裹
xxxContext.Provider
, 通过value
属性给后代组件传递数据:<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
const { Provider } = UserNameContext export default class A extends Component {state = {username: 'tom',age: 18}render() { const { username, age } = this.statereturn (<div className='parent'><h3>我是A组件</h3><h4>我的用户名是:{username}</h4><h4>我的年龄是:{age}</h4><Provider value={{ username, age }}><B /></Provider></div>)} }
-
后代组件读取数据:
第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收contextthis.context // 读取context中的value数据
class C extends Component {// 声明接收contextstatic contextType = UserNameContextrender() {console.log(this);console.log(this.context);const {username,age}=this.contextreturn (<div className='grand'><h3>我是C组件</h3><h4>我从A组件拿到的用户名是:{username}</h4><h4>我从A组件拿到的年龄时是:{age}</h4></div>)} }
第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>{value => ( // value就是context中的value数据//要显示的内容)}</xxxContext.Consumer>
// 创建context对象 const UserNameContext = React.createContext() const { Provider, Consumer } = UserNameContext function C() {return (<div className='grand'><h3>我是C组件</h3><Consumer>{value => {return (<div><h4>我从A组件拿到的用户名是:{value.username}</h4><h4>我从A组件拿到的年龄时是:{value.age}</h4></div>)}}</Consumer></div>) }
注意:
在应用开发中一般不用context, 一般都用它的封装react插件
6. 组件优化
Component的2个问题
-
只要执行
setState()
,即使不改变状态数据, 组件也会重新render()
==> 效率低export default class Parent extends Component {state = {carName: '奔驰'}changeName = () => {const { carName } = this.statethis.setState({})}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>我的车的名字是:{this.state.carName}</h4><button onClick={this.changeName}>点击换车</button></div>)} }
-
只要当前组件重新
render()
, 就会自动重新render
子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
export default class Parent extends Component {state = {carName: '奔驰',stus:['小王','小李','小芳']}changeName = () => {const { carName } = this.statethis.setState({})}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>我的车的名字是:{this.state.carName}</h4><button onClick={this.changeName}>点击换车</button><Child carName='奥拓'/></div>)}
}class Child extends Component {render() {console.log('child---render');return (<div className='child'><h3>我是Child组件</h3><h4>我接受到的车的名字是:{this.props.carName}</h4></div>)}
}
效率高的做法
只有当组件的
state
或props
数据发生改变时才重新render()
原因
Component
中的shouldComponentUpdate()
总是返回true
解决
办法1:
-
重写
shouldComponentUpdate()
方法 -
比较新旧
state
或props
数据, 如果有变化才返回true
, 如果没有返回false
// person组件 class Parent extends Component {state = {carName: '奔驰',}changeName = () => {const { carName } = this.statethis.setState({})}shouldComponentUpdate(nextProps,nextState){return !this.state.carName===nextState.carName} render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>我的车的名字是:{this.state.carName}</h4><button onClick={this.changeName}>点击换车</button></div>)} }
// Child组件 class Child extends Component {shouldComponentUpdate(nextProps,nextState){return !this.props.carName===nextProps.carName}render() {console.log('child---render');return (<div className='child'><h3>我是Child组件</h3><h4>我接受到的车的名字是:{this.props.carName}</h4></div>)} }
办法2:
-
使用
PureComponent
import React, { PureComponent} from 'react'
-
PureComponent
重写了shouldComponentUpdate()
, 只有state
或props
数据有变化才返回true
export default class Parent extends PureComponent {state = {carName: '奔驰',}changeName = () => {const { carName } = this.statethis.setState({})}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>我的车的名字是:{this.state.carName}</h4><button onClick={this.changeName}>点击换车</button><Child carName='奥拓'/></div>)} }class Child extends PureComponent {render() {console.log('child---render');return (<div className='child'><h3>我是Child组件</h3><h4>我接受到的车的名字是:{this.props.carName}</h4></div>)} }
-
注意:
-
只是进行
state
和props
数据的浅比较, 如果只是数据对象内部数据变了, 返回false
-
不要直接修改
state
数据, 而是要产生新数据export default class Parent extends PureComponent {state = {carName: '奔驰'}changeName = () => {const obj = this.stateobj.carName = '迈巴赫'console.log(obj === this.state);this.setState(obj)}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>我的车的名字是:{this.state.carName}</h4><button onClick={this.changeName}>点击换车</button></div>)} }
PureComponent
的底层做了一个浅对比,不会管Obj
里面的属性是否发生变化,只要Obj
与this.state
是同一个对象,就不会引起更新在之前更新数组类型的状态时说过不能通过使用
push
、unshift
等原生数组的方法改写数组来更新状态,需要返回一个新的数组进行更新export default class Parent extends PureComponent {state = {stus:['小王','小李','小芳']}addStus=() => {const {stus}=this.statestus.unshift('小刘')this.setState({stus})}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>学生有{this.state.stus}</h4><button onClick={this.addStus}>添加学生</button></div>)} }
需要通过
[新增的数组元素,...原数组]
的方式进行更新export default class Parent extends PureComponent {state = {stus:['小王','小李','小芳']}addStus=() => {const {stus}=this.statethis.setState({stus:['小刘',...stus]})}render() {console.log('parent---render');return (<div className='parent'><h3>我是Parent组件</h3><h4>学生有{this.state.stus}</h4><button onClick={this.addStus}>添加学生</button></div>)} }
-
-
项目中一般使用
PureComponent
来优化
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot
技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props
: 通过组件标签体传入结构
使用render props
: 通过组件标签属性传入结构,而且可以携带数据,一般用render
函数属性
children props
之前说过标签体的内容是标签特殊的属性
children
,可以通过this.props.children
进行读取,所以可以将B组件作为A组件的标签体写入
export default class Parent extends Component {render() {return (<div className='parent'><h3>我是Parent组件</h3><A> <B/> </A></div>)}
}
class A extends Component {state={name:'tom'}render() {const {name}=this.statereturn (<div className='a'><h3>我是A组件</h3>{this.props.children}</div>)}
}
class B extends Component {render() {return (<div className='b'><h3>我是B组件</h3></div>)}
}
问题: 如果B组件需要A组件内的数据 ==> 做不到
export default class Parent extends Component {render() {return (<div className='parent'><h3>我是Parent组件</h3><A> <B/> </A></div>)}
}
class A extends Component {state={name:'tom'}render() {const {name}=this.statereturn (<div className='a'><h3>我是A组件</h3>{this.props.children}</div>)}
}
class B extends Component {render() {return (<div className='b'><h3>我是B组件</h3><h3>从A获取到名字:{this.props.name}</h3></div>)}
}
render props
<A render={(data) => <C data={data}></C>}></A>
A组件:
{this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示{this.props.data}
export default class Parent extends Component {render() {return (<div className='parent'><h3>我是Parent组件</h3><A render={(name)=><B name={name}/>}/></div>)}
}
class A extends Component {state={name:'tom'}render() {const {name}=this.statereturn (<div className='a'><h3>我是A组件</h3>{this.props.render(name)}</div>)}
}
class B extends Component {render() {return (<div className='b'><h3>我是B组件</h3><h3>从A获取到名字:{this.props.name}</h3></div>)}
}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError
配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {console.log(error);// 在render之前触发// 返回新的statereturn {hasError: true,};
}
componentDidCatch(error, info) {// 统计页面的错误。发送请求发送到后台去console.log(error, info);
}
// Parent组件
export default class Parent extends Component {state={hasError:''//用于标识子组件是否产生错误}// 出错时的生命周期钩子 componentDidCatch(){console.log('渲染组件出错');}// 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息static getDerivedStateFromError(error){console.log(error);return {hasError:error}}render() {return (<div><h2>我是Parent组件</h2>{this.state.hasError?<h2>网络出错</h2>:<Child/>}</div>)}
}
// Child组件
export default class Child extends Component {state={/* users:[{id:'1',name:'may',age:18},{id:'2',name:'tom',age:21},{id:'3',name:'jerry',age:19}] */// 错误原因:users:''}render() {return (<div><h2>我是Child组件</h2><ul>{this.state.users.map((userObj)=>{return <li key={userObj.id}>名字{userObj.name}——年龄{userObj.age}</li>})}</ul></div>)}
}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
-
props
:children props
和render props
- 消息订阅-发布:
pubs-sub
、event
等等 - 集中式管理:
redux
、dva
等等 -
conText
:生产者-消费者模式
比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)