React Hooks 完整学习笔记

Stella981
• 阅读 528

https://juejin.im/post/5e687c26e51d45272443f7d8

时长:预计 30 min

面向:React Hook 初学者

版本:React 16.13.0 + TypeScript 3.7.2

原文:获得更好阅读体验

即使文中代码片段均可直接运行,仍然建议将源代码仓库拉到本地跑起来之后边调试边阅读。


一:设计动机

  1. 组件之间复用状态逻辑很难

  2. 复杂组件变得难以理解

  3. 难以理解的 Class

详细参考这里

二:使用姿势

  • useState

    import React from 'react'; const { useState } = React;

    // https://zh-hans.reactjs.org/docs/hooks-state.html

    // useState 是最简单的一个 hook // 唯一需要注意的是不要尝试在循环或条件等不稳定的代码结构中编写 // 原因在这里 -> https://github.com/brickspert/blog/issues/26

    export default function UseState() { const [num1, setNum1] = useState(0); const [num2, setNum2] = useState(0); console.warn('render'); return (

    useState Demo

    num1:{num1}

    num2:{num2}

    <button onClick={() => setNum1(num1 + 1)}>add num1 <button onClick={() => setNum2(n => n + 1)}>add num2
    ); } 复制代码

  • useEffect

    import React from 'react'; const { useState, useEffect } = React;

    // https://zh-hans.reactjs.org/docs/hooks-effect.html // https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/

    // 适用场景: // 1. 模拟钩子函数可以进行清理操作

    export default function UseEffect() { const [num1, setNum1] = useState(0); const [num2, setNum2] = useState(0);

    // 相当于 componentDidMount + componentDidUpdate + componentWillUnmount useEffect(() => { console.log('useEffect1'); return () => { console.log('_useEffect1'); }; });

    // 相当于 componentDidMount + componentWillUnmount // 注意 deps 参数为空数组,不同于不传!! useEffect(() => { console.log('useEffect2'); return () => { console.log('_useEffect2'); } }, []);

    // 相当于 componentDidMount + componentDidUpdate + componentWillUnmount // 相比于第一种情况更加精确 useEffect(() => { console.log('useEffect3'); return () => { console.log('_useEffect3'); } }, [num1]);

    console.warn('render'); return (

    useEffect Demo

    {num1}

    <button onClick={() => setNum1(num1 + 1)}>更新 num1

    {num2}

    <button onClick={() => setNum2(num2 + 1)}>更新 num2
    ); } 复制代码

  • useContext

    import React, { useContext } from 'react';

    const { useState, createContext } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#usecontext

    // 适用场景: // 1. 状态共享

    // 唯一需要注意的是: // 当组件上层最近的 <SizeContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 SizeContext provider 的 context value 值。 // 即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

    const SizeContext = createContext({ size: 0, // 给子组件更改 context value 值暴露接口 setSize(size: number) { console.log(size) }, });

    function ChildCom() { const { size, setSize } = useContext(SizeContext);

    function updatSize() { setSize(size + 1); } console.warn('child srender'); return (

    子组件 size:{size}

    ) }

    export default function UseContext() { const [size, setSize] = useState(0); console.warn('father render'); const value = { size, setSize } return (

    <SizeContext.Provider value={value}>

    根组件 size:{size}

    </SizeContext.Provider>
    ); }

    复制代码

  • useMemo

    import React from 'react'; const { useState, useMemo } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo

    // 适用场景: // 1. 性能优化:减少不必要的重复计算

    function getGreetText(name: string) { console.log('重新计算'); return 'hello' + name; }

    // function ChildNormal(props: { name1: string, name2: string }) { // // 不管是 name1 还是 name2 变化都会导致重新计算 // const greet = getGreetText(props.name1); // return ( // <> // {greet} // </> // ); // }

    function ChildUseMemo(props: { name1: string, name2: string }) { // 只有在 name1 变化时候才会计算 const greet = useMemo(() => getGreetText(props.name1), [props.name1]); console.log('child render'); return ( <>
    name1: {props.name1}
    name2: {props.name2}
    greet: {greet} </> ); }

    export default function UseMeno() { const [name1, setName1] = useState(''); const [name2, setName2] = useState(''); return (

    name1:<input type="text" onChange={(e) => setName1(e.target.value)} />

    name2:<input type="text" onChange={(e) => setName2(e.target.value)} />

    {/* greet normal: */}

    greet useMemo:
    ); } 复制代码

  • useCallback

    import React, { useCallback } from 'react';

    const { useState, memo } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#usecallback

    // useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

    // 适用场景: // 1. 性能优化:将句柄传入做了 memo 的子组件

    interface IChildProps { count: number; onAdd(): void; }

    const Child1 = memo(function (props: IChildProps) { console.log('Child1 Render'); return ( <div style={{ border: '1px solid #000' }}>

    Child1

    count:{props.count}
); });

const Child2 = memo(function (props: IChildProps) { console.log('Child2 Render'); return ( <div style={{ border: '1px solid #000' }}>

Child2

count:{props.count}
); });

export default function UseCallback() { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0);

const onAddCount1 = () => { setCount1(count1 + 1); }

// 标记是稳定的,count1 变化不会影响 Child2 的渲染 const onAddCount2 = useCallback(() => { setCount2(count2 + 1); }, [count2]);

console.log('Wrap Renders'); return (


); } 复制代码

  • useLayoutEffect

    import React from 'react';

    const { useState, useLayoutEffect, useEffect } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#uselayouteffect

    // 适用场景: // 1. 解决闪烁问题

    function Child1() { const [value, setValue] = useState(0);

    // 异步更新会出现闪烁 useEffect(() => { if (value === 0) { setValue(10 + Math.random() * 200); } }, [value]);

    console.log('render', value);

    return (

    {value === 0 ?

    xiixix

    :

    value: {value}

    } <button onClick={() => setValue(0)}>click me
    ); }

    function Child2() { const [value, setValue] = useState(0);

    // 同步更新将不会出现闪烁 useLayoutEffect(() => { if (value === 0) { setValue(10 + Math.random() * 200); } }, [value]);

    console.log('render', value);

    return (

    {value === 0 ?

    xiixix

    :

    value: {value}

    } <button onClick={() => setValue(0)}>click me
    ); } export default function UseLayoutEffect() { return (


    ); } 复制代码

  • useRef

    import React, { useEffect, ChangeEvent, useState } from 'react'; const { useRef } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#useref export default function UseRef() { const [num, addNum] = useState(0); // 1. 用于获取 DOM const ref1 = useRef(null); // 2. 用作实例属性的存储,useRef 在整个组件生命周期都会保持不变 const ref2 = useRef('0');

    useEffect(() => { console.log('num', ref2.current); console.log('ref2.current', ref2.current); });

    const onClick = () => { // current 指向已挂载到 DOM 上的文本输入元素 ref1.current?.focus(); };

    const onChange = (e: ChangeEvent) => { ref2.current = e.target.value }

    console.warn('render'); return (

    useRef Demo

    <button onClick={() => addNum(num + 1)}>addNum
    ); } 复制代码

  • useImperativeHandle

    import React from 'react'; const { useRef, useImperativeHandle, forwardRef } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#useimperativehandle // 适用场景: ref 转发时候代理一层,做 API 的上层封装

    interface IChildRef { getHeight(): number; }

    const Child = forwardRef((props: {}, ref: React.Ref) => { const divRef = useRef(null);

    useImperativeHandle(ref, () => ({ getHeight: () => { console.log('计算了高度'); return divRef.current?.clientHeight || 0; } }));

    return ( <div ref={divRef} style={{ height: '100px', width: '100px', border: '1px solid #000' }}> i am child

  • ); });

    export default function UseImperativeHandle() { const childRef = useRef(null);

    function getChildHeight() { console.log(childRef.current?.getHeight()); }

    return (

    useImperativeHandle Demo
    ); } 复制代码

  • useReducer

    import React from 'react';

    const { useReducer } = React;

    // https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer

    // 适用场景; // 1. 复杂的数据类型,需要差量更新 // 2. 可以获取到上一次的数据 // 3. 性能优化:稳定的 dispatch 句柄

    function reducer(state: { count: number }, action: { type: 'increment' | 'decrement' }) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }

    export default function UseReducer() { console.warn('render1'); const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <> Count: {state.count}

    <button onClick={() => dispatch({ type: 'decrement' })}>- <button onClick={() => dispatch({ type: 'increment' })}>+ </> ); } 复制代码

  • useContext + useReducer 实现全局共享状态

    import React from 'react';

    const { useReducer, createContext, useContext } = React;

    type ISizeTypes = 'addWidth' | 'addHeight'; type ISize = { width: number, height: number }

    const SizeContext = createContext([ { width: 0, height: 0 }, a => { } ]) as React.Context<[ISize, (a: { type: ISizeTypes }) => void]>;

    const useSize = () => { const [size, dispatch] = useContext(SizeContext); function addWidth() { dispatch({ type: 'addWidth' }); } function addHeight() { dispatch({ type: 'addHeight' }); } return { size, addWidth, addHeight }; };

    function reducer(state: ISize, action: { type: ISizeTypes }) { switch (action.type) { case 'addWidth': return { ...state, width: state.width + 1 }; case 'addHeight': return { ...state, height: state.height + 1 }; default: throw new Error(); } }

    function Com1() { const { size, addHeight, addWidth } = useSize(); return (

    组件1
    宽度: {size.width}
    高度: {size.height}
    ); }

    function Com2() { const { size, addHeight, addWidth } = useSize(); return (

    组件2
    宽度: {size.width}
    高度: {size.height}
    ); }

    export default function UseReducer() { console.warn('render1'); const sizeContext = useReducer(reducer, { width: 0, height: 0 }); return ( <> <SizeContext.Provider value={sizeContext}>


    </SizeContext.Provider> </> ); } 复制代码

  • 两个原则:

    1. 只在最顶层使用 Hook,不要在循环,条件或嵌套函数中调用 Hook(这是为什么?)

    2. 只在 React 函数中调用 Hook

    三:自定义 Hooks

    这部分建议直接阅读市面上一些优秀 hooks 库的源码:

    react-use

    awesome-react-hooks

    @umi/hooks


    参考资料:

    点赞
    收藏
    评论区
    推荐文章
    blmius blmius
    2年前
    MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
    文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
    Easter79 Easter79
    2年前
    swap空间的增减方法
    (1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
    Jacquelyn38 Jacquelyn38
    2年前
    2020年前端实用代码段,为你的工作保驾护航
    有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
    皕杰报表之UUID
    ​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
    Wesley13 Wesley13
    2年前
    Java获得今日零时零分零秒的时间(Date型)
    publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
    Wesley13 Wesley13
    2年前
    mysql设置时区
    mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
    Wesley13 Wesley13
    2年前
    00:Java简单了解
    浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
    Stella981 Stella981
    2年前
    Django中Admin中的一些参数配置
    设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
    Wesley13 Wesley13
    2年前
    MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
    背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
    Python进阶者 Python进阶者
    3个月前
    Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
    大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这