React hooks应用及常用扩展

275 阅读3分钟

Hook简介

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

State Hook

import React, { Component } from 'react'
function Demo(){
    // 声明一个叫 "count" 的 state 变量
    const [count,setCount] = React.useState(0)
    function add(){
        setCount(count+1)   //第二种写法:setCount(count => count+1)
    }
    return (
        <div>
            <h1>当前count值为:{count}</h1>
            <button onClick={add}>点我加1</button>
        </div>
    )
}

Effect Hook

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

import React, { Component } from 'react'
function Demo(){
    const [count,setCount] = React.useState(0)
    //useEffect可以在函数组件中用于模拟类组件中的生命钩子
    React.useEffect(()=>{
        let timer = setInterval(() => {
            setCount(count=>count+1)  // componentDidmount componentDidUpdate
        }, 1000);
        return ()=>{
            clearInterval(timer)   //相当于在componentWillUnmount里清除定时器
        }
    },[count]) 
    function add(){
        setCount(count=>count+1)
    }
    function unmount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }
    return (
        <div>
           <h1>当前count值为:{count}</h1>
           <button onClick={add}>点击加1</button>
           <button onClick={unmount}>点击卸载组件</button>
        </div>
    )
}

useEffect(() => { return () => {} }, []),第二个参数如果指定的是[], 回调函数只会在第一次render()后执行;上述代码中[count]代表检测count,回调函数会在count每次改变后执行;如果省略第二个参数,代表检测所有变量。

Ref Hook

类似类组件中的React.createRef()

function Demo(){
    const myRef = React.useRef()  //创建
    function show(){
       //使用
       alert(myRef.current.value)
    }
    return (
        <div>
            {/*绑定*/}
            <input type="text" ref={myRef}/>   
            <button onClick={show}>点我提示数据</button>
        </div>
    )
}

setState更新状态的写法

import React, { Component } from 'react'
class Demo extends Component {
    state = {count:0}
    add = ()=>{
        const {count} = this.state
        //setState(stateChange, [callback])
        //callback是可选函数,在状态更新和界面更新后调用
        //1.对象形式
        // this.setState({count:count+1},()=>{
        //     console.log(this.state.count)   
        // })

        //2.函数形式
        this.setState((state,props)=>{
            return {count:state.count+1}
        })
    }
    render(){
        return (
            <div>
                <h1>当前求和为:{this.state.count}</h1>
                <button onClick={this.add}>点我加1</button>
            </div>
        )
    }
}

Fragment

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

function Demo(){
    //文档碎片作用代替外层div  Fragment只能有一个key属性
    const {Fragment} = React
    return (
        <Fragment>
            <h1>Fragment...</h1>
        </Fragment>
    )
}

Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

const Context = React.createContext() 
const {Provider,Consumer} = Context
function DemoOne(){
    const [name] = React.useState('sandwich')
    return (
        <div>
            <Provider value={name}>
                <DemoTwo />
            </Provider>                   
        </div>
    )
}
function DemoTwo(){
        //第一种方式:仅适用于类组件 
        //static contextType = Context  声明接收context
        //this.context   读取context中的value数据
     return (
         <div>
            {/*第二种方式,通用*/}
            <Consumer>
                {
                    value => <h3>----DemoOne传递过来的值:{value}</h3>
                }
            </Consumer>
        </div>
     )
}

组件优化

Component 的2个问题

  • 只要执行setState(),即使不改变状态数据, 组件也会重新render()
  • 只要当前父组件重新 render(), 就会自动重新 render 子组件,效率低

原因:Component 中的shouldComponentUpdate()总是返回true

解决办法:

  1. 重写shouldComponentUpdate()方法,比较新旧 state 或 props 数据, 如果有变化才返回true, 如果没有返回 false
  2. 使用 PureComponent,PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回 true
class Parent extends React.PureComponent {
    state = {count:1}
    test = ()=>{
        this.setState({})  //只是调用setState,并没有改变任何状态
    }
    render(){
        console.log('parent-render')  //输出一次
        return (
            <div>
                <h1>Parent</h1>
                <button onClick={this.test}>点击按钮</button>
                <Child/>
            </div>
        )
    }
}
class Child extends React.PureComponent {
    render(){
        console.log('child-render')   //输出一次
        return (
            <div>Child</div>
        )
    }
}

zz.gif

Render Props

具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。

   // 类似 Vue 中的组件插槽
class Parent extends React.Component {
   render(){
       return (
        <div>
           <A render={data=><B data={data}></B>}></A>
        </div>
       )
   }
}
class A extends React.Component {
    state={test:'A需要给B传递的数据'}
    render(){
       const {test} = this.state
       return(
        <div>
           {/*调用传递过来的render函数展示B组件,并传递数据(实参)*/}
           {this.props.render(test)}
        </div>
       )
    }
}
class B extends React.Component{
    render(){
        return (
            <div>
            {/*接收A传递过来的数据*/}
            <h3>{this.props.data}</h3>
        </div>
        )
    }
}

错误边界

用来捕获后代组件错误,渲染出备用页面 使用方式:getDerivedStateFromError配合componentDidCatch

特点:使用错误边界,当后代组件只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。

class Parent extends React.Component {
    state={
        hasError:''   //用于标识子组件是否产生错误
    }
    //Parent的子组件出现错误时便会调用这个钩子
    static getDerivedStateFromError(error){
        console.log('子组件出错了')
        return {hasError:error}
    }
    componentDidCatch(){
        console.log('渲染组件时出错,通知编码人员出了bug')
    }
    render(){
        return (
            <div>
                {this.state.hasError ? <h2>当前网络不稳定,请稍后再试</h2> : <Child/>}
            </div>
        )
    }
}
class Child extends React.Component {
    state = { users:''}
    render(){
        const {users} = this.state
        return (
            <div>
                {
                    users.map((item)=>{
                        return <li key={item.id}>{item.id}---{item.name}</li>
                    })
                }
            </div>
        )
    }
}

上述代码 users是空字符,Child中遍历 users 便会出错,使用错误边界后不会使整个页面崩溃。

image.png