8.1 setState
概要总结
1、setState更新状态的2种写法
2、两种setState的对比
一、setState更新状态的2种写法
1、setState(stateChange, [callback])——对象式的setState
(1)stateChange为状态改变对象(该对象可以体现出状态的更改)
(2)callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用
import React, {Component} from 'react';
export default class Demo extends Component {
state = {count: 0}
add = () => {
// 1.获取原来的count值
const {count} = this.state
// 2.更新状态
this.setState({count: count + 1})
console.log('12行的输出', this.state.count)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}h1>
<button onClick={this.add}>点我+1button>
div>
);
}
}
react在更新状态的时候是一个异步操作。调了setState之后,react帮我们改状态。setState本身是同步的,但是react改状态的动作是异步的。如果想在状态更新完之后输出,可以使用setState的第二个参数:回调函数。
add = () => {
// 1.获取原来的count值
const {count} = this.state
// 2.更新状态
this.setState({count: count + 1}, () => {
console.log(this.state.count)
})
// console.log('12行的输出', this.state.count)
}
2、setState(updater, [callback])——函数式的setState
(1)updater为返回stateChange对象的函数
(2)updater可以接收到state和props
(3)callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用
函数式跟对象式的区别在于,对象式直接传一个对象,而函数式是传一个函数,通过返回值作为状态更新对象。
add = () => {
// 函数式的setState
this.setState((state, props) => ({count: state.count + 1}), () => {
console.log(this.state.count)
})
}
函数式的函数里还带有state和props,如果改变状态需要依赖原状态,它就显得比对象式更为方便一些。
二、两种setState的对比
1、对象式的setState是函数式的setState的简写方式(语法糖)
2、使用原则
(1)如果新状态不依赖于原状态 => 使用对象方式
(2)如果新状态依赖于原状态 => 使用函数方式
(3)如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
8.2 lazyLoad
概要总结
1、懒加载的lazy用法
2、懒加载的suspense用法
一、lazy的使用
路由组件默认会把所有的组件一次性全部加载完毕,它不管你是否会访问其他路由,只要一注册就全部加载。
lazyLoad就是路由的懒加载,匹配路由加载对应的组件。首先在react库引入lazy,然后把组件的直接import引入改成用lazy方法引入。
import React, {Component, lazy} from 'react';
import {NavLink, Route} from 'react-router-dom'
// import About from './About'
// import Home from './Home'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default class App extends Component {
render() {
return (
......
{/* 注册路由 */}
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
);
}
}
二、suspense的使用
使用lazy对组件进行懒加载之后,它会报如下错误:
这个报错的意思是:现在用了lazy实现懒加载,如果由于其他原因加载不出来,你要用suspense里的fallback指定一个组件,相当于设置一个默认组件来显示。
首先在react库引入suspense标签,然后用它把注册的路由都包裹起来,最后指定fallback的组件。
Loading组件:
import React, {Component} from 'react';
export default class Loading extends Component {
render() {
return (
<div>
<h1 style={{backgroundColor: 'gray', color: 'orange'}}>Loading....</h1>
</div>
);
}
}
suspense标签的fallback引入Loading组件:
import React, {Component, lazy} from 'react';
import {NavLink, Route} from 'react-router-dom'
// import About from './About'
// import Home from './Home'
import Loading from './Loading'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default class App extends Component {
render() {
return (
......
<Suspense fallback={<Loading/>}>
{/* 注册路由 */}
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Suspense>
);
}
}
注意事项:
1、Loading组件作为路由懒加载的默认组件,它不可以使用lazy懒加载。因为suspense的fallback本身的用意是防止组件懒加载时间过长的时候,先把默认组件显示出来,如果默认组件也是懒加载,那就违背了设计的原则。
2、默认组件一定要在懒加载组件的前面引入,如果在后面引入会报错如下:
import React, {Component, lazy} from 'react';
import {NavLink, Route} from 'react-router-dom'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
import Loading from './Loading'
export default class App extends Component {
render() {
return (
......
<Suspense fallback={<Loading/>}>
{/* 注册路由 */}
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Suspense>
);
}
}
8.3 stateHook
概要总结
1、React.useState的用法
2、函数式组件调用次数
3、useState方法的两种形式
4、一个useState对应一个状态
一、React.useState
在函数式组件里,是没有this的,那么也同样无法使用state。hooks提供了React.useState,顾名思义就是使用state。有了useState,函数式组件也能使用state。
React.useState()需要传入一个默认值,然后返回一个数组,里面有两个项,分别是状态值和更新状态的方法。
例如总数count默认为0,方法是setCount,那么就可以写成const [count, setCount] = React.useState(0)
function Demo() {
const [count, setCount] = React.useState(0)
// 加的回调
function add() {
setCount(count + 1)
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
</div>
);
}
二、函数式组件调用次数
函数式组件毕竟是一个函数,它每次渲染都必须要重新调用整个函数才可以实现。所以它的调用策略是1+n,跟类式的render函数是一样的。
函数的重新调用,必然会重新执行useState(),此时它又把状态的默认值重新传过去,正常来说状态会被还原。在这里React做了处理,它会把状态先存起来,就算整个函数被重新执行,useState重新被调用,状态是不会还原到初始值的。
三、useState方法的两种形式
useState第一个参数是初始值,第二个参数是改变状态的方法,这个方法可以直接传状态值过去,也可以传一个方法。这个跟setState是非常像的。
const [count, setCount] = React.useState(0)
// 加的回调
function add() {
// setCount(count + 1) // 第一种写法
setCount(count => count + 1)
}
四、一个useState对应一个状态
一个useState只能对应一个状态,如果多个状态则需要使用多个useState。
function Demo() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('tom')
// 加的回调
function add() {
// setCount(count + 1) // 第一仲写法
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>
);
}
8.4 EffectHook
概要总结
1、React.useEffect的用法
2、React.useEffect对应的生命周期钩子
一、React.useEffect
React.useEffect的作用是给函数式组件添加了生命周期钩子。
1、useEffect第一个参数
useEffect的第一个参数是一个回调函数,当初始化组件以及任何一个状态的改变,它都会执行,也就是1+n次。
function Demo() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log('@')
})
// 加的回调
function add() {
setCount(count => count + 1)
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
</div>
);
}
2、useEffect第二个参数
useEffect的第二个参数是一个数组,它的作用是控制监听的状态。如果第二个参数不传,默认所有状态都会监听;如果传了空数组,那就是所有状态都不监听;如果需要选择某些状态进行监听,那就可以通过数组进行控制。
function Demo() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('tom')
React.useEffect(() => {
console.log('@')
}, [name])
// 加的回调
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>
);
}
这里第二个参数传的是[name],因此它只监听name状态的变化,而count状态并不监听。
3、useEffect第一个参数的返回值
useEffect在第一个参数里,它还有一个返回值,它是一个函数,它是在组件即将销毁前才会执行,就相当于componentWillUnmount生命钩子。
function Demo() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('tom')
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
// 加的回调
function add() {
// setCount(count + 1) // 第一仲写法
setCount(count => count + 1)
}
function unmount() {
// ReactDOM.unmountComponentAtNode(document.getElementById('root'))
root.unmount(document.getElementById('root'))
}
return (
<div>
<h2>当前求和为:{count}</h2>
<h2>我的名字是:{name}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
注意:在旧版的React的销毁方法是ReactDOM.unmountComponentAtNode(document.getElementById('root')),在18版之后的销毁方法是root.unmount(document.getElementById('root'))。ReactDOM在react-dom库引入,root在index.js那里导出,然后才可以引入:
// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入App组件
import App from './App'
import {BrowserRouter} from 'react-router-dom'
const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(
<BrowserRouter>
<App/>
</BrowserRouter>
)
export default root
二、useEffect对应的生命周期钩子
1、componentDidMount
componentDidMount生命钩子其实是对应useEffect的第二个参数传空数组[],因为传了空数组,它就只会在初始化的时候执行一次,然后不监听所有状态,它就不会执行第二次了。
2、componentDidUpdate
componentDidUpdate生命钩子是在状态更新的时候执行,其实就是useEffect不传第二个参数(不传默认监听所有状态)或者传入第二个参数,数组指定某个状态的监听。
3、componentWillUnmount
componentWillUnmount生命钩子就是对应useEffect第一个参数里的返回值。
8.5 RefHook
概要总结
1、React.useRef的用法
一、React.useRef
React.useEffect与类式组件的React.createRef完全一致。
function Demo() {
const [count, setCount] = React.useState(0)
const myRef = React.useRef()
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
// 加的回调
function add() {
// setCount(count + 1) // 第一仲写法
setCount(count => count + 1)
}
// 提示输入的回调
function show() {
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef}/>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={show}>点我提示数据</button>
</div>
)
}
8.6 Fragment
概要总结
1、Fragment的用法
2、<>的用法
3、Fragment与<>的区别
一、Fragment标签
在jsx语法中,dom元素的最外层一定要包裹一个标签,例如:
export default class Demo extends Component {
render() {
return (
<div a="1">
<input type="text"/>
<input type="text"/>
</div>
);
}
}
但有时候我们不希望它额外多了一层标签,此时可以用Fragment作为根标签,它在最终解析到页面上的时候,标签会被去掉,它的目的就是绕过jsx的语法。
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"/>
<>
);
}
}
三、Fragment与<>的区别
与<>的作用是相同的,惟一的区别是标签还可以多接收一个key属性,这个是针对遍历循环需要传key的时候设定的,而<>标签是不能传任何属性。
8.7 Context
概要总结
1、原始组件通信
2、React.createContext()
3、函数式组件使用Context
Context是一种组件间的通信方式。常用于【祖组件】与【后台组件】间通信。
一、原始组件通信
在原始的组件间通信,基本都是通过父子组件的props传参实现。如果祖孙之间通信,那就把状态从祖传到父,再从父传到孙。
export default class A extends Component {
state = {username: 'tom'}
render() {
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{this.state.username}</h4>
<B username={this.state.username}/>
</div>
);
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名:{this.props.username}</h4>
<C username={this.props.username}/>
</div>
);
}
}
class C extends Component {
render() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从B组件接收到的用户名:{this.props.username}</h4>
</div>
);
}
}
二、React.createContext()
原始通信方式有两个缺点,一个是组件层级过多的时候,数据传递非常繁琐;另一个是哪怕组件自身不需要用到的状态,但为了要传给后代组件,它也只能去接收然后再传递,给组件增加不少工作量。
其实每个组件的实例除了有props、state和refs之外,还有一个context,这个context就是用来给祖组件与后代组件的联系。
1、React.createContext创建Context实例
我们可以使用React.createContext()创建Context的实例对象,这个对象必须放在所有组件都能访问的位置,就相当于一个公共区域。
import React, {Component} from 'react';
import './index.css'
// 创建Context对象
const MyContext = React.createContext()
export default class A extends Component {...}
class B extends Component {...}
class C extends Component {...}
2、Context的Provider属性实现传递状态
创建Context实例之后,需要用实例的Provider属性把子组件包裹起来。包裹了之后,它的所有后代组件都可以从Context中接收状态。
Provider有一个value属性,它是用来传状态的。这个Provider与Redux的Provider极其相像,也是在Provider传入状态,所有后代组件都能使用。
// 创建Context对象
const MyContext = React.createContext()
const {Provider} = MyContext
export default class A extends Component {
state = {username: 'tom'}
render() {
const {username} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={username}>
<B/>
</Provider>
</div>
);
}
}
class B extends Component {...}
class C extends Component {...}
3、子组件声明接收Context
虽然在祖组件通过Provider传入了状态,但后代组件如果想使用,得必须先声明接收Context,否则无法获取。
需要通过static关键字进行声明。
// 创建Context对象
const MyContext = React.createContext()
const {Provider} = MyContext
export default class A extends Component {
state = {username: 'tom'}
render() {
const {username} = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={username}>
<B/>
</Provider>
</div>
);
}
}
class B extends Component {...}
class C extends Component {
// 声明接收context
static contextType = MyContext
render() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从B组件接收到的用户名:{this.context}</h4>
</div>
);
}
}
4、Provider传多个状态
Provider的value属性可以传对象,通过对象把所有状态传过去,取的时候根据键值对取即可。
// 创建Context对象
const MyContext = React.createContext()
const {Provider} = MyContext
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>
<Provider value={{username, age}}>
<B/>
</Provider>
</div>
);
}
}
class B extends Component {...}
class C extends Component {
// 声明接收context
static contextType = MyContext
render() {
const {username, age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从B组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
}
三、函数式组件使用Context
Context实例不仅有Provider,还有Consumer。Provider只能在类式组件使用,而Consumer是两种类型的组件都可以。
它的用法是在标签里面使用一个函数,通过value值把需要使用Context内容的值连同DOM结构一同渲染出来。
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从B组件接收到的用户名:
<Consumer>
{
value => `${value.username},年龄是${value.age}`
}
</Consumer>
</h4>
</div>
);
}
四、总结
1、 创建Context容器对象。
const XxxContext = React.createContext()
2、渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3、后代组件读取数据:
(1)第一种方式:仅适用于类组件
static contextType = xxxContent // 声明接收context
this.context // 读取context中的value数据
(2)第二种方式:函数组件与类组件都可以
{
value => { // value就是context中的value数据
要显示的内容
}
}
8.8 PureComponent
概要总结
1、Component的缺陷
2、Component缺陷原因
3、使用shouldComponentUpdate生命钩子优化
4、PureComponent的使用
一、Component的缺陷
1、只要执行setState(),即使不改变状态数据,组件也会重新render() ===> 效率低
以下案例是一个父子组件,这里执行了setState(),传入的是一个空对象,也就是实际上什么状态都没有更新,但即便如此,父组件以及相关子组件都把render()执行了一遍。
import React, {Component} from 'react';
import './index.css'
export default class Parent extends Component {
state = {carName: '奔驰c63'}
changeCar = () => {
this.setState({})
}
render() {
console.log('Parent---render')
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件h3>
<span>我的车名字是:{carName}span><br/>
<button onClick={this.changeCar}>点我换车button>
<Child/>
<div>
);
}
}
class Child extends Component {
render() {
console.log('Child---render')
return (
<div className="child">
<h3>我是Child组件h3>
div>
);
}
}
2、当前组件重新render(),就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ===> 效率低
以下案例是一个父子组件,子组件并没有用到父组件的数据,但由于父组件的状态更新,子组件也会执行render(),虽然可以理解但并不太合理。
import React, {Component} from 'react';
import './index.css'
export default class Parent extends Component {
state = {carName: '奔驰c63'}
changeCar = () => {
this.setState({carName: '迈巴赫'})
}
render() {
console.log('Parent---render')
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件h3>
<span>我的车名字是:{carName}span><br/>
<button onClick={this.changeCar}>点我换车button>
<Child/>
<div>
);
}
}
class Child extends Component {
render() {
console.log('Child---render')
return (
<div className="child">
<h3>我是Child组件</h3>
</div>
);
}
}
二、Component缺陷原因
Component中的shouldComponentUpdate()总是返回true。因为react需不需要调用render函数,实际上关键在于shouldComponentUpdate钩子,它是一个阀门,它返回ture就更新,false就不更新。但是它默认是返回true的。
三、使用shouldComponentUpdate生命钩子优化
只有当组件的state或props数据发生改变时才重新render()。
我们可以使用shouldComponentUpdate生命钩子来判断是否更新,它有两个参数:nextProps和nextState,分别对应下一个props和state。我们可以借助它们与当前的props和state做一个比较来决定是否更新。
1、根据state状态判断是否更新
export default class Parent extends Component {
state = {carName: '奔驰c63'}
changeCar = () => {
this.setState({carName: '迈巴赫'})
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log(this.props, this.state) // 接下来要变化的目标props,目标state
console.log(nextProps, nextState) // 目前的props和state
return this.state.carName !== nextState.carName
}
......
}
changeCar = () => {
this.setState({})
}
2、根据props状态判断是否更新
这里子组件是固定传入一个值,父组件改变状态的时候,子组件就可以根据前后两个props比较,从而不引发render函数执行。
import React, {Component} from 'react';
import './index.css'
export default class Parent extends Component {
state = {carName: '奔驰c63'}
changeCar = () => {
this.setState({carName: '迈巴赫'})
}
render() {
console.log('Parent---render')
const {carName} = this.state
return (
<div className="parent">
......
<Child carName="奥拓"/>
<div>
);
}
}
class Child extends Component {
shouldComponentUpdate(nextProps, nextState, nextContext) {
return this.props.carName !== nextProps.carName
}
render() {
console.log('Child---render')
return (
<div className="child">
<h3>我是Child组件h3>
<span>我接到的车是:{this.props.carName}span>
<div>
);
}
}
四、PureComponent的使用
PureComponent的作用就是帮我们做好了上述的阀门处理,优化了Component所带来的缺陷,不需要我们手动去解决。
import React, {PureComponent} from 'react';
import './index.css'
export default class Parent extends PureComponent {
state = {carName: '奔驰c63'}
changeCar = () => {
this.setState({carName: '迈巴赫'})
}
render() {
console.log('Parent---render')
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件</h3>
<span>我的车名字是:{carName}</span><br/>
<button onClick={this.changeCar}>点我换车</button>
<Child carName="奥拓"/>
</div>
);
}
}
class Child extends PureComponent {
render() {
console.log('Child---render')
return (
<div className="child">
<h3>我是Child组件</h3>
<span>我接到的车是:{this.props.carName}</span>
</div>
);
}
}
注意:PureComponent会对state和props做一个浅比较。所谓浅比较就是普通类型对比值,引用类型对比地址。如果值相同,那么阀门就会关闭不做更新。
8.9 renderProps
概要总结
1、子组件显示标签体内容
2、子组件显示的标签体也是一个组件
3、renderProps的使用
4、renderProps的优势
一、子组件显示标签体内容
父组件引入子组件的时候,带有标签体内容,它实际上相当于给子组件传入了children属性,子组件可以通过this.props.children把它读取出来。
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件h3>
<A>hello!A>
div>
);
}
}
class A extends Component {
render() {
return (
<div className="a">
<h3>我是A组件h3>
{this.props.children}
<div>
);
}
}
二、子组件显示的标签体也是一个组件
不管标签体的内容是什么,想要渲染出来那都是一个套路:this.props.children。
import React, {Component} from 'react';
import './index.css'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件h3>
<A>
<B/>
A>
<div>
);
}
}
class A extends Component {
render() {
return (
<div className="a">
<h3>我是A组件h3>
{this.props.children}
<div>
);
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件h3>
<div>
);
}
}
三、renderProps的使用
对于<A><B/></A>这种形式而言,A和B自身是不知道谁是父组件和子组件,它们的父子关系是在它们的祖组件决定的,而且这种关系是随着祖组件的写法而改变的,例如<B><A/></B>,它们的关系就逆转了。
在祖组件使用<A><B/></A>的形式,也存在一个问题,那就是没有办法把A组件的数据传递给B,因为代码是写在了祖组件里。
renderProps的概念是,祖组件不使用<A><B/></A>的形式,而是在里传递一个render参数,里面是一个函数,返回值是子组件。例如: <A render={() => <B/>}}/>。在A组件想展示B组件,可以通过调用props的render方法,把B组件渲染出来。
import React, {Component} from 'react';
import './index.css'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件h3>
<A render={() => <B/>} />
<div>
);
}
}
class A extends Component {
render() {
return (
<div className="a">
<h3>我是A组件h3>
{this.props.render()}
<div>
);
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件h3>
<div>
);
}
}
注意:renderProps这种写法可以任意传参,因为render里是一个函数,把需要传的参数在render()里传进去,那边就可以接收,然后传给子组件。
import React, {Component} from 'react';
import './index.css'
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.state
return (
<div className="a">
<h3>我是A组件h3>
{this.props.render(name)}
<div>
);
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件, {this.props.name}h3>
<div>
);
}
}
四、renderProps的优势
对于A组件而言,它其实并不关心它嵌套的子组件到底是哪个,它就负责把子组件展示在这个位置,就相当于插槽的概念。
例如A组件里渲染其它组件,只需要改变render里的组件名即可:
import React, {Component} from 'react';
import './index.css'
import C from '../1_setState'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件h3>
<A render={name => <C name={name}/>} />
<div>
);
}
}
class A extends Component {
state = {name: 'tom'}
render() {
const {name} = this.state
return (
<div className="a">
<h3>我是A组件h3>
{this.props.render(name)}
<div>
);
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件, {this.props.name}h3>
<div>
);
}
}
五、总结
1、如何向组件内部动态传入带内容的结构(标签)?
Vue中:使用slot技术,也就是通过组件标签体传入结构
React中:
使用children props:通过组件标签体传入结构
使用render props:通过组件标签属性传入结构,一般用render函数属性
2、children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题:如果B组件需要A组件内的数据,做不到。
3、render props
<A render={data => } />
A组件:{this.props.render(内部state数据)
C组件:读取A组件传入的数据显示 {this.props.data}
8.10 ErrorBoundary概要总结
概要总结
1、错误边界概念
2、getDerivedStateFromError生命钩子
3、componentDidCatch生命钩子
一、错误边界概念
由于某些不可控的因素,包括程序员代码出错、后端数据出问题、服务器崩溃等等,会导致代码无法正常运行。
错误边界的作用是把错误控制在一个局部范围,不要让局部的出错影响了全局。这个错误边界是在父组件设置的,不是在子组件设置。
二、getDerivedStateFromError生命钩子
getDerivedStateFromError生命钩子是在它的子组件发生错误的时候执行,它跟getDerivedStateFromProps钩子一样,最后要返回一个state对象。
我们可以在有可能发生错误的子组件进行一个错误边界处理:
import React, {Component} from 'react';
import Child from './Child'
export default class Parent extends Component {
state = {
hasError: ''
}
static getDerivedStateFromError(error) {
console.log(error)
return {hasError: error}
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
);
}
}
注意:这个错误边界处理需要在生产环境下才能生效。
三、componentDidCatch生命钩子
componentDidCatch生命钩子经常跟getDerivedStateFromError生命钩子配合使用,它也是在子组件报错的时候调用,他主要是用来捕获错误,做一个错误的统计,反馈给后台服务器等。
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>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试h2> : <Child/>}
<div>
);
}
}
四、总结
1、理解
错误边界(Error Boundary):用来捕获后代组件错误,渲染出备用页面
2、特点
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
3、使用方式
getDerivedStateFromError配合componentDidCatch
// 声明周期函数,一旦后代组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true
}
}
componentDidCatch(error, info) {
// 统计页面的错误,发送请求到后台去
console.log(error, info)
}