第8章:React扩展

105 阅读19分钟

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 {NavLinkRoutefrom '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, {Componentfrom '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 {NavLinkRoutefrom '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 {NavLinkRoutefrom '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 {BrowserRouterfrom '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, {ComponentFragmentfrom 'react';

export default class Demo extends Component {
  render() {
    return (
      <Fragment>
        <input type="text"/>
        <input type="text"/>
      </Fragment>
    );
  }
}

二、<>

       除了可以用标签去掉根标签,还可以用空标签<>,它的作用也是去掉根标签。

import React, {ComponentFragmentfrom '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'age18}

  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, {PureComponentfrom '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, {Componentfrom '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)
}

8.11 代码地址

gitee.com/huang_jing_…