1、setState
setState
更新状态的两种写法:
1、对象式的setState
setState(stateChange, [callback])
stateChange
为状态改变对象(该对象可以体现出状态的更改);callback
是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用;
2、函数式的setState
setState(updater, [callback])
updater
为返回stateChange
对象的函数;updater
可以接收到state
和props
;callback
是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用;
总结:
1、对象式setState
是函数式setState
的简写方式(语法糖)
2、使用原则:
- 如果新状态不依赖于原状态 ===> 使用对象方式;
- 如果新状态依赖于原状态 ===> 使用函数方式;(使用对象式或函数式看个人爱好需求吧,不作强制要求)
- 如果需要在
setState()
执行后获取最新的状态数据,要在第二个callback
函数中读取;
import React, { Component } from 'react'
export default class Demo extends Component {
state = {count:0}
add = ()=>{
// 对象式的setState
/* const {count} = this.state
this.setState({count:count+1},()=>{
console.log('异步',this.state.count); // 1
})
//console.log('同步',this.state.count); // 0 */
// 函数式的setState
// 简写
// this.setState( state => ({count:state.count+1}))
this.setState( (state, props) => {
console.log(state, props) // {count: 0} {aaa: "123456"}
return {count:state.count+1}
})
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
</div>
)
}
}
2、懒加载lazyLoad
React和Vue都是单页面应用。在单页应用中,未设置懒加载,webpack打包后的文件就会很大。造成首页加载的资源过多,时间太长,产生首屏空白情况。运用懒加载,在需要的时候加载对应的页面。分担首屏加载压力,减少首页加载时间。
react是一种按需引入的机制,所以在使用时需要从react核心库中解构出
lazy
和Suspense
;
lazy
函数:用于实现组件的懒加载式引入;
Suspense
:用于在 因网络延迟等而导致的组件不能快速的加载到页面 时出现的提示;
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.路由组件未加载出来时,生成一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3、React Hooks
React Hook/Hooks是什么?
Hook是React 16.8.0版本增加的新特性/新语法,可以让你在函数组件中使用 state 以及其他的 React 特性
三个常用的Hook
State Hook: React.useState()
Effect Hook: React.useEffect()
Ref Hook: React.useRef()
3.1、State Hook
State Hook让函数组件也可以有state状态,并进行状态数据的读写操作;
语法: const [xxx, setXxx] = React.useState(initValue)
useState()
说明:
- 参数:第一次初始化指定的值在内部作缓存;
- 返回值:包含两个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数;
setXxx()
两种写法:
setXxx(newValue)
:参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值;setXxx(value => newValue)
:参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值;
import React from 'react'
import ReactDOM from 'react-dom'
//类式组件
/* class Demo extends React.Component {
state = {count:0}
add = ()=>{
this.setState(state => ({count:state.count+1}))
}
render() {
return (
<div>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我+1</button>
</div>
)
}
} */
function Demo(){
const [count,setCount] = React.useState(0)
function add(){
// setCount(count+1) //写法一
// 写法二
setCount(count => count+1 )
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
</div>
)
}
export default Demo
3.2、Effect Hook
Effect Hook
可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作如:发ajax请求数据获取、设置订阅 / 启动定时器、手动更改真实DOM等等…
语法和说明:
// useEffect函数有两个参数:
// 第一个参数为一个函数,参数函数中的内容为你要填写的副作用操作;参数函数return一个函数,return函数中的内容在组件被卸载前执行一次(相当于componentWillUnmount)
// 第二个参数可写可不写:
// 1、不写,则表示监听所有的state中数据,界面初始化执行一次以及每次state中数据更新的时候也执行;(相当于componentDidMount,componentDidUpdate)
// 2、写[], 则表示不监听任何数据,界面初始化执行一次;(相当于componentDidMount)
// 3、写[count], 则表示只监听state中的count数据,界面初始化执行一次以及每次state中的count数据更新的时候也执行;(相当于componentDidMount,componentDidUpdate)
useEffect(() => {
// 在此可以执行任何带副作用操作,界面挂载的时候执行一次或数据更新的时候执行n次
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, []) // 如果指定的是[], 回调函数只会在第一次render()后执行
总结:useEffect Hook相当于如下三个生命周期函数的总和
componentDidMount()
、componentDidUpdate()
、componentWillUnmount()
import React from 'react'
import ReactDOM from 'react-dom'
//类式组件
/* class Demo extends React.Component {
state = {count:0}
componentDidMount(){
this.timer = setInterval(()=>{
this.setState( state => ({count:state.count+1}))
},1000)
}
componentWillUnmount(){
clearInterval(this.timer)
}
unmount = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
render() {
return (
<div>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.unmount}>卸载组件</button>
</div>
)
}
} */
function Demo(){
const [count,setCount] = React.useState(0)
React.useEffect(()=>{
let timer = setInterval(()=>{
setCount(count => count+1 )
},1000)
return ()=>{
clearInterval(timer)
}
},[])
//卸载组件的回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
export default Demo
3.3、Ref Hook
Ref Hook
可以在函数组件中存储/查找组件内的标签或任意其它数据;
语法:const myRef = React.useRef()
作用:保存标签对象,功能与React.createRef()
一样
import React from 'react'
import ReactDOM from 'react-dom'
//类式组件
/* class Demo extends React.Component {
myRef = React.createRef()
show = ()=>{
alert(this.myRef.current.value)
}
render() {
return (
<div>
<input type="text" ref={this.myRef}/>
<button onClick={this.show}>点击提示数据</button>
</div>
)
}
} */
function Demo(){
const myRef = React.useRef()
//提示输入的回调
function show(){
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef}/>
<button onClick={show}>点我提示数据</button>
</div>
)
}
export default Demo
4、Fragment
1、Fragment
相当于Vue中的template
标签,最大作用就是不用必须有一个真实的DOM根标签;
2、也可以使用<></>
的语法来声明 Fragments
,它看起来像空标签,但是 Fragments
支持key
属性,而 <></>不支持任何属性;
import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
state = {
list: [1,2,3,4,5,6,7]
}
render() {
const {list} = this.state
return (
<ul>
{list.map(item => (
// 没有`key`,React 会发出一个关键警告
<Fragment key={item}>
<li>{item}</li>
</Fragment>
))}
</ul>
);
}
}
5、Context
上下文提供了一种通过组件树传递数据的方法,而不必在每个级别手动传递props;
Context使用:
1、创建Context容器对象:const XxxContext = React.createContext()
2、渲染子组时,外面包裹xxxContext.Provider
,通过value
属性给后代组件传递数据;
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3、后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
const {xxx} = this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
import React, { Component } from 'react'
import './index.css'
//创建Context对象
const MyContext = React.createContext()
export default class A extends Component {
state = {username:'tom',age:18}
render() {
const {username,age} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<MyContext.Provider value={{username,age}}>
<B/>
</MyContext.Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C/>
</div>
)
}
}
/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
function C(){
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<MyContext.Consumer>
{value => `${value.username},年龄是${value.age}`}
</MyContext.Consumer>
</h4>
</div>
)
}
6、组件优化
Component的两个问题
1、只要执行
setState()
,即使不改变状态数据,组件也会重新render()
2、只要当前组件重新render()
,就会自动重新render
子组件 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决方案
方法一:
- 重写
shouldComponentUpdate()
方法; - 比较新旧state或props数据,如果有变化才返回true,如果没有返回false;
- 对于复杂数据比较的时候就很麻烦了,不推荐使用,了解即可;
// state中数据都未改变,父子组件都不会重新render; // 只更新父组件中的数据count,未更新子组件依赖的数据name,只更新父组件的render; // 更新父子组件都依赖的数据name,父子组件都重新render; import React, { Component } from 'react' export default class Parent extends Component { state = { count: 100, name: 'aaa' } clickHandle = () => { this.setState({}) } countChange = () => { const {count} = this.state this.setState({count: count + 1}) } nameChange = () => { this.setState({name: 'bbb'}) } shouldComponentUpdate(nextProps, nextState, nextContext) { // console.log(this.props,this.state); //目前的props和state // console.log(nextProps,nextState); //接下要变化的目标props,目标state return !(this.state.count === nextState.count && this.state.name === nextState.name) } render() { console.log('render Parent') const {name} = this.state return ( <div> <h3>我是Parent组件</h3> <div>{this.state.count}</div> <div>{this.state.name}</div> <button onClick={this.clickHandle}>button</button> <button onClick={this.countChange}>count change</button> <button onClick={this.nameChange}>name change</button> <Child name={name}/> </div> ) } } class Child extends Component{ shouldComponentUpdate(nextProps, nextState, nextContext) { // console.log(this.props,this.state); //目前的props和state // console.log(nextProps,nextState); //接下要变化的目标props,目标state return !(this.props.name === nextProps.name) } render() { console.log('render Child') return ( <div> <h3>我是Child组件</h3> <div>{this.props.name}</div> </div> ) } }
方法二:
- 使用
PureComponent
,PureComponent
重写了shouldComponentUpdate()
,只有state或props数据有变化才返回true;
注意事项:
- 只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false;
- 不要直接修改state数据, 而是要产生新数据;
- 项目中一般使用
PureComponent
来优化;
// state中数据都未改变,父子组件都不会重新render;
// 只更新父组件中的数据list,未更新子组件依赖的数据name,只更新父组件的render;
// 更新父子组件都依赖的数据name,父子组件都重新render;
import React, { PureComponent } from 'react'
export default class Parent extends PureComponent {
state = {
name:"奔驰",
list:['小张','小李','小王']
}
addlist = ()=>{
// 错误写法,不要直接修改state数据, 而是要产生新数据;
// const {list} = this.state
// list.unshift('小刘')
// this.setState({list})
// 正确写法
const {list} = this.state
this.setState({list:['小刘',...list]})
}
nameChange = ()=>{
// 错误写法,下面写法相当于this.state.name = '迈巴赫',直接修改了state数据
// const obj = this.state
// obj.name = '迈巴赫'
// console.log(obj === this.state); // true
// this.setState(obj)
// 正确写法
this.setState({name:'迈巴赫'})
}
render() {
console.log('render Parent');
const {name} = this.state
return (
<div className="parent">
<h3>我是Parent组件</h3>
<div>{this.state.list}</div>
<div>{this.state.name}</div>
<button onClick={this.nameChange}>name change</button>
<button onClick={this.addlist}>添加小刘</button>
<Child name={name}/>
</div>
)
}
}
class Child extends PureComponent {
render() {
console.log('render Child');
return (
<div className="child">
<h3>我是Child组件</h3>
<div>{this.props.name}</div>
</div>
)
}
}
7、render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:使用slot
技术,也就是通过组件标签体传入结构
React中:
使用children props
:通过组件标签体传入结构;
使用render props
:通过组件标签属性传入结构, 一般用render
函数属性;
children props
import React, { Component } from 'react'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
// 在A组件中插入B组件
<A><B/></A>
</div>
)
}
}
class A extends Component {
render() {
console.log(this.props);
return (
<div className="a">
<h3>我是A组件</h3>
// 在A组件的这个位置,加载B组件
{this.props.children}
</div>
)
}
}
class B extends Component {
render() {
console.log('B--render');
return (
<div className="b">
<h3>我是B组件</h3>
</div>
)
}
}
问题:如果B组件需要A组件内的数据,做不到
render props
import React, { Component } from 'react'
import C from '../1_setState'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
// 在A组件中传入自身的数据到B、C组件中
<A render={(name)=><C name={name}/>}/>
<A render={(name)=><B name={name}/>}/>
</div>
)
}
}
class A extends Component {
state = {name:'tom'}
render() {
console.log(this.props);
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
// 在A组件的这个位置,加载组件
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
console.log('B--render');
return (
<div className="b">
// 读取A组件传过来的数据,并展示
<h3>我是B组件,{this.props.name}</h3>
</div>
)
}
}
<A render={(data) => <C data={data}></C>}></A>
A组件:{this.props.render(内部state数据)}
挂载插槽中的组件或标签
C组件:读取A组件传入的数据显示 {this.props.data}
8、错误边界
错误边界用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError
配合componentDidCatch
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
// 打包上线的项目,子组件报错时展示h2标签中的内容;
// 本地项目,子组件报错时,展示一秒中h2中的内容,立马跳转成界面报错;
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
state = {
users:[
{id:'001',name:'tom',age:18},
{id:'002',name:'jack',age:19},
{id:'003',name:'peiqi',age:20},
]
// users:'abc'
}
componentDidMount() {
// 报错部分
this.state.aaa.map(item => console.log(item))
}
render() {
return (
<div>
<h2>我是Child组件</h2>
{
this.state.users.map((userObj)=>{
return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
})
}
</div>
)
}
}
上线项目报错
本地开发项目报错
9、组件通信方式总结
方式:
1、props:
- children props
- render props
2、消息订阅-发布:
- pubs-sub、event等等
3、集中式管理:
- redux、dva等等
4、Context:
- 生产者-消费者模式
组件间如何选择通信方式?
- 父子组件:props;
- 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理;
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、Context(用的少);