React教程详解四(hooks、pureComponent、Context通信、错误边界、children props与render props)

前言

hooks是react16.8.0版本新增加的新特性/新语法,最大的特点是可以在开发者在函数组件中使用state以及其它React特性,下面分别对其介绍~

React.useState()

state hook能让函数组件也可以拥有state状态,方便其进行state状态的读写操作~

React.useState()方法的参数为第一次初始化state的值,返回一个包含两个元素的数组,第一个为内部当前状态值,另一个参数是更新状态值的函数

const [xxx, setXXX] = React.useState(initValue)

setXXX方法有两种写法:

setXXX(newValue) // 非函数参数,直接将此值作为新的state值
setXXX(value => newValue) // 函数作为参数,接收原来的状态值,返回新的状态值

setXXX方法会自动将内存中储存的state值进行更新~

// components/Students.jsx
import React from 'react'

export default function Students() {
    const [msg, setMsg] = React.useState({name: '小明', age: 12})
    const [grade, setGrade] = React.useState('99') // 可通过useState传递多个状态
    function show() {
        // setMsg({name: '小红', age: 13}) // 直接将新的状态值返回
        setMsg((old) => ({name: '小红', age: old.age+1})) // 参数返回新的状态值,可对旧状态值进行操作
    }
    function change() {
        setGrade('100')
    }
  return (
    <div>
        <h2>这是学生信息</h2>
        <h3>姓名:{msg.name}</h3>
        <h3>年龄:{msg.age}</h3>
        <h3>成绩:{grade}</h3>
        <button onClick={show}>更改学生信息</button>
        <button onClick={change}>更改学生成绩</button>
    </div>
  )
}

React.useEffect()

Effect hook可以让你在函数组件中使用,达到使用声明周期函数效果;

useEffect(() => {
    // 做一些操作
    return () => { 
        // 如果写了return,则该函数可以在卸载前被调用,
        // 相当于类组件中的componentWillUnmount
     }
},[stateValue] // 此处若指定了stateValue,则就会在组件render和更新时被调用,若指定为空数组时,则只在渲染时被调用
)

可以讲useEffect hook看作是三个声明函数的组合:

componentDidMount()
componnetDidUpdate()
componentWillUnmount()
import React from 'react'
// import ReactDOM from 'react-dom';
import {root} from '../index'

export default function Test() {
  const [number, setNumber] = React.useState(0)
  function add() {
    setNumber(value => value+1)
  }
  function destory() {
    root.unmount()
  }
React.useEffect(() => {
    const timer = setInterval(() => {
        setNumber(value => value+1)
    }, 500)
    return () => {clearInterval(timer)}
},[])
  return (
    <div>
        <span>test组件</span>
        <span>数值是{number}</span>
        <button onClick={add}>点我加一</button>
        <button onClick={destory}>销毁组件</button>
    </div>
  )
}

React.useRef()

ref hook可以在函数组件中保存标签对象,功能与类组件标签中的React.createRef()一样React教程详解一(props、state、refs、生命周期)_迷糊的小小淘的博客-CSDN博客

const xxxRef = React.useRef() // 定义一个ref

在标签中标记只需使用ref=xxxRef即可,取值利用xxxRef.current

import React from 'react'

export default function Students() {
    const [msg, setMsg] = React.useState({name: '小明', age: 12})
    const ageRef = React.useRef() // 定义标签ref
    function show() {
       alert(ageRef.current.innerText); // 取值
    }
  return (
    <div>
        <h2>这是学生信息</h2>
        <h3>姓名:{msg.name}</h3>
        <h3 ref={ageRef}>年龄:{msg.age}</h3>
        <button onClick={show}>显示学生年龄</button>
    </div>
  )
}

其它关于路由的hooks已在上一篇文章中讲过,请移步React教程详解二(脚手架、路由)_迷糊的小小淘的博客-CSDN博客

PureComponent

在使用类组件式,会继承于React的Component组件,该组件存在两个问题:

  • 只要执行setState(),即使不改变状态数据,组件也会重新渲染(render)
  • 若当前组件重新render,即使子组件并未发生任何变化,也会重新渲染

这两个问题导致组件经常被渲染,导致效率低下,因此想要让其当组件的props或state数据发生变化时才重新渲染;

导致此问题的原因在于Component中的shouldComponentUpdate()总是返回true,因此要让其有选择性的进行返回,有两种方法可以做到:

  • 重写每个组件的shouldComponentUpdate(nextProps, nextState)方法,该方法接收未来的props和state,将其与目前的state/props进行比较,若发生改变才返回true,否则为false
import React, { Component } from 'react'

export default class Students extends Component {
    state = {
        name: '小明', 
        age: 10
    }
    show = () => {
        this.setState({name: '小明', age: 11})
    }
    shouldComponentUpdate(nextProps, nextState) {
        return !(nextState.name === this.state.name && nextState.age === this.state.age)
    }
  render() {
    console.log('是否被渲染'); // state中age改变了,所以此行会被输出
    const {name, age} = this.state
    return (
        <div>
            <h2>这是学生信息</h2>
            <h3>姓名:{name}</h3>
            <h3>年龄:{age}</h3>
            <button onClick={this.show}>显示学生年龄</button>
        </div>
    )
  }
}
  • 使用PureComponent替换Component,PureComponent重写了shouldComponentUpdate方法,保证组件只有在state或者props变化的时候返回true(当然是进行了浅比较,所以若直接修改了state也是会引起页面渲染的)
import React, { PureComponent } from 'react'

export default class Students extends PureComponent {
    state = {
        name: '小明', 
        age: 10
    }
    show = () => {
        this.setState({name: '小明', age: 10})
    }
  render() {
    console.log('是否被渲染'); // state中age未改变,所以此行不会被输出
    const {name, age} = this.state
    return (
        <div>
            <h2>这是学生信息</h2>
            <h3>姓名:{name}</h3>
            <h3>年龄:{age}</h3>
            <button onClick={this.show}>显示学生年龄</button>
        </div>
    )
  }
}


开发中常用第二种方法提高运行效率~

Context组件通信

Context也是一种组件通信方式,常用于【祖组件】与【后代组件】通信,但还是redux香哈哈哈哈

使用时要先创建一个Context对象:

const xxxContext = React.createContext() // 创建Contex容器对象

主要利用Context对象上的Provider与Consume两个标签; 

在后代组件外部包裹xxxContext.Provider,并在该标签上添加value属性,此值即为要传给【后代组件】的数据;

const {Provider} = xxxContext  // 在祖组件中使用Provider

<Provider value={
   
   {school: '清华'}}>
  子组件
</Provider> // 包裹后代组件

【后代组件】读取数据有两种方式:

  • 在要接收数据的子组件声明static变量再读取---适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中value数据
  • 使用Consumer组件获取数据----类组件与函数组件都可以使用
const { Consumer } = xxxContext  // 在子组件中使用Consumer

<Consumer>
  {
      value => { // value就是context中的value数据
        // 要展示的内容
       }
   }
</Consumer>

示例:

①子组件是类组件形式:

// pages/School.jsx
import React, { Component } from 'react'
import Teacher from './Teacher'
import { SchoolContext } from './Students' // 因为要先加载子组件,所以将创建context定义在子组件上,在祖组件上引入
export const {Provider, Consumer} = SchoolContext

export default class School extends Component {
  state = {
    school: '清华',
    base: '北京'
  }
  render() {
    const {school, base} = this.state
    return (
      <div style={
   
   {backgroundColor: 'blue', padding: '10px'}}>
        <h1>这是学校:{school}</h1>
        <Provider value={
   
   {school, base}}> // 利用value传过去数据
          <Teacher />
        </Provider>
      </div>
    )
  }
}

// pages/Teacher.jsx 
import React, { Component } from 'react'
import Students from "./Students"
export default class Teacher extends Component {
  render() {
    return (
      <div style={
   
   {backgroundColor: 'yellow', padding: '10px'}}>
        <h2>这是老师</h2>
        <Students />
      </div>
    )
  }
}

//pages/Students.jsx
import React, { Component } from 'react'
export const SchoolContext = React.createContext() // 创建context对象并暴露出去
export default class Students extends Component {
  static contextType = SchoolContext
  render() {
    const {school, base} = this.context // 传过来的参数放在组件实例对象的context里
    return (
      <div style={
   
   {backgroundColor: 'orange', padding: '10px'}}>
        <h2>该生来自{base}的{school}</h2>
      </div>
    )
  }
}

②子组件是函数组件形式:

// pages/School.jsx
import React, { Component } from 'react'
import Teacher from './Teacher'
import { Provider } from './Students' // 引入context对象中的Provider

export default class School extends Component {
  state = {
    school: '清华',
    base: '北京'
  }
  render() {
    const {school, base} = this.state
    return (
      <div style={
   
   {backgroundColor: 'blue', padding: '10px'}}>
        <h1>这是学校:{school}</h1>
        <Provider value={
   
   {school, base}}>
          <Teacher />
        </Provider>
      </div>
    )
  }
}

// pages/Teacher.jsx
import React, { Component } from 'react'
import Students from "./Students"
export default class Teacher extends Component {
  render() {
    return (
      <div style={
   
   {backgroundColor: 'yellow', padding: '10px'}}>
        <h2>这是老师</h2>
        <Students />
      </div>
    )
  }
}

// pages/Students.jsx
import React, { Component } from 'react'

export const SchoolContext = React.createContext() // 创建context对象并暴露出去
export const {Provider, Consumer} = SchoolContext // 主要是为了暴露出Provider供祖组件使用

export default function Students() {
    return (
      <div style={
   
   {backgroundColor: 'orange', padding: '10px'}}>
        <Consumer> 
          {
            value => { // value相当于this.context,直接取里面的数据即可
              return (<h2>该生来自{value.base}的{value.school}</h2>)
            }
          }
        </Consumer>
      </div>
    )
}

react中组件通信方式主要有如下四种方式:

  1. props
  2. 消息订阅与发布机制,如pubsub-js
  3. 集中式状态管理,如redux
  4. 生产者消费者模式,如conText

错误边界

错误边界(error boundary)用来捕获后代组件在生命周期内产生的错误,从而渲染备用页面;

通常利用getDerivedStateFromError与componentDidCatch(非必需)配合达到效果;两个函数均要定义在父组件中,用于监测容易出错的子组件;

import React, { Component } from 'react'

export default class Parents extends Component {
    state = {hasError: ''} // 表示子组件是否有错
  // 在父组件中定义错误边界,当子组件在生命周期中报错时,会调用该函数
  static getDerivedStateFromError(error) {
    return {hasError: error}
  }
  componentDidCatch() {
    // 该函数可以不写,此处一般用于统计出错次数
  }
  render() {
    return (
      <div>
        <span>这是parents组件</span>
        {/* 判断容易报错的子页面 */}
         {this.state.hasError ? <span>页面出错了。。。(或者此处放一个精心做好的报错页面)</span>: <Son />}
      </div>
    )
  }
}
class Son extends Component {
    state = {}
    render() {
      return (
        <div>
          <span>这是son组件</span>
          <span>{this.state.name.name}</span>
          {/* 请求了一个不存在的数据,会报错 */}
        </div>
      )
    }
  }

children props与render props

之前所有讲过的例子中,三级组件使用都是采用如下方式:

// 祖组件中调用父组件
// 父组件中调用子组件

若想采用在祖组件中直接调用父组件和子组件方式应该采用什么方式呢?以上述讲解context中三个组件为例:

  • 通过props中children标签属性传递
// pages/School.jsx
import React, { Component } from 'react'
import Teacher from './Teacher'
import Students from './Students'

export default class School extends Component {
  state = {
    school: '清华',
    base: '北京'
  }
  render() {
    const {school, base} = this.state
    return (
      <div style={
   
   {backgroundColor: 'blue', padding: '10px'}}>
        <h1>这是学校:{school}</h1>
          <Teacher>
            <Students /> // 将子组件作为标签体内容传入
          </Teacher>
      </div>
    )
  }
}

// pages/Teacher.jsx
import React, { Component } from 'react'
export default class Teacher extends Component {
  render() {
    return (
      <div style={
   
   {backgroundColor: 'yellow', padding: '10px'}}>
        <h2>这是老师</h2>
        {/* 标签体中的内容通过this.props.children拿到,此时代表Students组件 */}
        {this.props.children} 
      </div>
    )
  }
}

// pages/Students.jsx正常写组件即可

该种方法利用了组件标签标签体中传递的方式,和一般组件不同的是,组件标签的标签体不会被主动渲染,需要在组件中对应位置声明{this.props.children} 进行接收才会展示~

该种方式没法传递数据给子组件,所以引入下面方式:

  • 通过props中render方法

组件标签可以传递render props属性渲染组件,且可以传递参数,相当于在父组件中做了占位,可以任意调用子组件。类似于vue中的插槽方法~

render属性为函数形式,接收传给子组件的参数,一般返回一个组件;在父组件中对应位置调用render方法,并传递给父组件的参数,实现父子间通信;即为{ this.props.render(xx参数1,xx参数2,...., xx参数n)}

// pages/School.jsx
import React, { Component } from 'react'
import Teacher from './Teacher'
import Students from './Students'

export default class School extends Component {
  render() {
    return (
      <div style={
   
   {backgroundColor: 'blue', padding: '10px'}}>
        <h1>这是学校</h1>
        {/* 通过render属性实现父子组件关系,通过prop传递参数*/}
        <Teacher render={({teacherName, subject}) => <Students teacherName={teacherName} subject={subject}/>} />
      </div>
    )
  }
}

// pages/Teacher.jsx
import React, { Component } from 'react'
export default class Teacher extends Component {
  state = {
    teacherName: '小李',
    subject: '语文'
  } 
  render() {
    const {teacherName, subject} = this.state
    return (
      <div style={
   
   {backgroundColor: 'yellow', padding: '10px'}}>
        <h2>这是老师</h2>
        {/* 在展示位置使用{this.props.render渲染子组件} */}
        {this.props.render({teacherName, subject})}
      </div>
    )
  }
}

// pages/Students.jsx
import { Component } from "react"
export default class Students extends Component {
    render() {
      return (
        <div style={
   
   {backgroundColor: 'orange', padding: '10px'}}>
         {/* 利用props接收参数 */}
         <h2>该生的{this.props.subject}老师是{this.props.teacherName}</h2>
        </div>
      )
    }
}



做笔记是真不容易啊(灬ꈍ ꈍ灬)

猜你喜欢

转载自blog.csdn.net/ks795820/article/details/129465490
今日推荐