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 版本

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容