React Hooks

先来看看类组件的生命周期Class 组件的生命周期

Class 组件的生命周期

相对以前较老的生命周期,新增了 getDerivedStateFromPropsgetSnapshotBeforeUpdate 两个生命周期,移除了(目前还未完全移除)三个生命周期:componentWillMountcomponentWillReceivePropscomponentWillUpdate。除此之外,还新增一个 this.forceUpdate(callback) 跳过该组件的 shouldComponentUpdate() 强制执行 render

一、为什么改生命周期?

  • 移除 componentWillMount 的原因是,初始化 state 可以使用 constructor 代替,发送 Ajax 可以使用 componentDidMount
  • 移除 componentWillUpdate 的原因是:新增的 getSnapshotBeforeUpdate 如果有返回值,componentDidUpdate的第三个参数(snapshot)能接受到,所以 getSnapshotBeforeUpdate 就是为了能和 conponentDidUpdate 产生关系。
  • 移除 componentWillReceiveProps 的原因是: componentWillReceiveProps
    使用机会很少,基本只会出现在 state 的值在任何时候都取决于 props 的情况下。新增的 getDerivedStateFromProps 明显是扩充了 componentWillReceiveProps 的功能。

更详细的原因,参考:

二、无状态组件(函数式组件)

functional component 一个函数就是一个组件,但是这个函数必须:

  • 大写字母开头、必须 Return jsx
  • 这个函数式组件:没有 state、没有生命周期、有 props。

增加了一种函数的调用方式:

import React,{Component,useState} from "react";
function Fun(){
    return <h1>两次调用</h1>
}
export default class App extends Component{
    constructor(){
        super();
    }
    render(){
        return (
                <div>
                    {Fun()}
                    <Fun />
                </div>
            );
    }
}

hooks 就是添加了状态和生命周期的函数组件。

三、生命周期方法要如何对应到 Hook?

  • constructor:初始化 state。hooks 中可使用 useState 和 useReducer 代替。
  • render:就是函数组件本身。
  • getDerivedStateFromProps:为 在渲染时 安排一次更新,即在 render 之前执行一次,对于 hooks 来讲就是在 render 语句上面加个条件判断语句。
  • shouldComponentUpdate:React.memo 只能浅比较(Object.is) props,不能对比state, React.memo
  • componentDidMount, componentDidUpdate, componentWillUnmountuseEffect Hook 的三种用法。
  • getSnapshotBeforeUpdatecomponentDidCatch 以及 getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,官网说会很快添加。

四、useState 和 setState 的区别

  • useState替换采用的是直接替换模式,setState 采用的是 合并 模式。所以 useState 要使用扩展运算符,而且 useState 得内容不能过多,多的话分多个 useState

使用 useState 制作计数器

import React, { Component, useState } from "react";
function Fun() {
    const [state, setState] = useState(10);
    return <div>
        <h1>{state}</h1>
        <button onClick = {()=>{setState(state + 1)}}>按我加一</button>
    </div>
}
export default class App extends Component{
    constructor() {
        super();
    }
    render() {
        return (
                <div>
                    <Fun />
                </div>
            );
    }
}

useState 是 react 自带的一个 hook 函数,它的作用就是用来声明状态变量。useState 这个函数接收的参数是我们的状态初始值,它返回了一个数组,这个数组的第 [0] 项是当前当前的状态值,第 [1] 项是可以改变状态值的方法函数。


计数器.gif
  • 另一个特别重要的就是闭包对 useState 的影响。

源代码:

import React, { useState } from "react";
import ReactDOM from "react-dom";
function A () {
    const [count, setCount] = useState(0);
    function handleAdd(){
        setTimeout(function delay () {
            setCount(count + 1);
        }, 3000);
    };
    return (
        <>
            <h1>{count}</h1>
            <button onClick={handleAdd}>加一</button>
        </>
    )
}

ReactDOM.render(<A />, document.getElementById("container"));

代码演示结果,连续点击三下,最终只变了一次,count 变量不能正确记录实际点击次数,有些点击被吃掉,就是闭包的影响,handleAdd 函数是 A 函数里面的一个函数,典型的闭包函数, handleAdd 函数内部记住了 A 函数的变量。为了解决这个问题,使用函数方法来更新 count 状态

改写代码:

import React, { useState } from "react";
import ReactDOM from "react-dom";
function A () {
    const [count, setCount] = useState(0);
    function handleAdd(){
        setTimeout(function delay () {
            setCount(value =>  value + 1);
        }, 3000);
    };
    return (
        <>
            <h1>{count}</h1>
            <button onClick={handleAdd}>加一</button>
        </>
    )
}

ReactDOM.render(<A />, document.getElementById("container"));

此时快速单击按钮。延迟过去后,count 能正确表示点击次数。

三、useEffect 的作用

useEffect 相当于 react 的这三个生命周期( componentDidMount,componentDidUpdate和componentWillUnmount)函数的结合:

  • componentDidMount
useEffect(() => {
    console.log("componentDidMount");
}, []);
  • componentDidMount + componentWillUnmount
useEffect(() => {
    console.log("componentDidMount");
    return () => {
        console.log("componentWillUnmount");
    };
}, []);
  • componentDidMount 和 componentDidUpdate
useEffect(() => {
    console.log("componentDidMount 和 componentDidUpdate");
}, [state]);

一个失败的例子🌰:

import React,{Component,useState,useEffect} from "react";
function Fun(){
    const [state, setState] = useState(10);
    useEffect(() => {
        console.log("观察我的生命周期!");
        console.log("componentDidMount,componentDidUpdate和componentWillUnmount");
    })
    return <div>
        <h1>{state}</h1>
        <button onClick = {()=>{setState(state + 1)}}>按我加一</button>
    </div>
}
export default class App extends Component{
    constructor(){
        super();
    }
    render(){
        return (
                <div>
                    <Fun />
                </div>
            );
    }
}
未测试将要下树

四、hook API

不一个一个介绍了,反正自己看直接看写法就行了。

  • useMemouseEffect 的执行时机
    useEffect 执行的是副作用,所以一定是在渲染之后执行的,useMemo 是需要有返回值的,而返回值可以直接参与渲染的,所以 useMemo 是在渲染期间完成的,useMemo 先于 useEffect 执行

  • userMemouseCallback 的区别?
    共同点:

  1. 只有在依赖数据发生变化后,才会重新计算结果,起到缓存的作用
  2. 接受的参数都是一样的,第一个参数为回调函数,第二个参数是要依赖的数据;

区别:

  1. useMemo 返回的是计算的结果值,用于缓存计算后的状态,一般用于缓存 state。
  2. useCallback 返回的是函数,主要用来缓存函数。

仔细品,是不是有 Vue computed API 的赶脚,这有一篇介绍 React 中使用 computed 的文章:乾坤大挪移!React 也能 “用上” computed 属性 —— memoize-one

  • 获取 DOM 的三个 API
  1. useRef 函数内容获取 DOM
  2. useImperativeHandle(ref, createHandle, [deps]) 结合 React.forwardRef 可以转发经过改写的 ref。 React.forwardRef 解决函数组件的实例没有 ref 的问题。
  • useLayoutEffect
    与 useEffect 用法相同,但它是在浏览器执行绘制之前,同步触发重渲染,有可能阻塞代码,禁用。
  • useContextuseReducer
  1. useContext 多组件共享状态,结合 React.createContext 使用。
  2. useReducer 就是 redux ,但是相对于 redux 没有中间件功能。

五、调色板

import React,{Component,useState,useEffect} from "react";
function Fun(){
    const [r, setR] = useState(10);
    const [g, setG] = useState(10);
    const [b, setB] = useState(10);
    return <div>
        <div style = {{
        "width":"200px",
        "height":"200px",
        "backgroundColor":`rgb(${r},${g},${b})`
    }}></div>
        <p>
            R:{r}<input type = "range" min = {"0"} max = {"255"} value={r} onChange = {(e)=>{
                setR(e.target.value);
            }}/>
        </p>
        <p>
            G:{g}<input type = "range" min = {"0"} max = {"255"} value={g} onChange = {(e)=>{
                setG(e.target.value);
            }}/>
        </p>
        <p>
            B:{b}<input type = "range" min = {"0"} max = {"255"} value={b} onChange = {(e)=>{
                setB(e.target.value);
            }}/>
        </p>
    </div>
}
export default class App extends Component{
    constructor(){
        super();
        this.state = {
            a : 10
        }
    }
    render(){
        return (
                <div>
                    <Fun />
                </div>
            );
    }
}
调色板.gif

现在应用还在测试阶段,但 React 官方说了一句话,Hooks 将成为 React 官方推荐的写法!

六、路由和 redux 支持 hooks 版本

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容