react hooks常用Api梳理

1、useEffect

作用:处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class Component的componentDidMount、componentDidUpdate、componentWillUnmount等生命周期

  • 无参数:
    不传递第二个参数可以模拟componentDidMount、componentDidUpdate,注意和传递第二个参数的区别,任何修改都调用useEffect更新。
useEffect(()=>{
 // ...do something
})
  • 传递空数组
    可以模拟componentDidMount
useEffect(()=>{
 // ...do something
}, [])
  • 传递第二个参数
    可以模拟componentDidMount和componentDidUpdate
useEffect(()=>{
 console.log(count);
}, [count]) //count更新执行
  • return
    可以模拟componentDidUpdate
useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  return () => clearInterval(id); // 在组件销毁的时候执行
}, []);

2、useContext

用一个简单的示例来做辅助理解。

import React, { createContext, useContext, useState } from "react";

const initialState = { m: 100, n: 50 }; // 定义初始state
const Store = createContext({}); // 创建Context

export default function App() {
  const [state, setState] = useState(initialState); // 创建state读写接口
  return (
    <Store.Provider value={{ state, setState }}> // value值传递变量。
      <Father></Father>
    </Store.Provider>
  );
}

const Father = (props) => {
  console.log("Father");
  const { state, setState } = useContext(Store); //拿到 名字为Store的上下文的value,用两个变量来接收读写接口
  const addN = () => {
    setState((state) => {
      return { ...state, n: state.n + 1 };
    });
  };
  const addM = () => {
    setState((state) => {
      return { ...state, m: state.m + 1 };
    });
  };
  return (
    <div>
      爸爸组件
      <div>n:{state.n}</div>
      <Child />
      <button onClick={addN}>设置n</button>
      <button onClick={addM}>设置m</button>
    </div>
  );
};
const Child = (props) => {
  console.log("son");
  const { state } = useContext(Store); // 读取state
  return (
    <div>
      儿子组件
      <div>m:{state.m}</div>
    </div>
  );
};

3、useReducer

useState的替代方案,useReducer只是一个小范围内的状态管理工具。在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

useReducer的使用场景:
1、如果你的state是一个数组或者对象
2、如果你的state变化很复杂,经常一个操作需要修改很多state
3、如果你希望构建自动化测试用例来保证程序的稳定性
4、如果你需要在深层子组件里面去修改一些状态(关于这点我们下篇文章会详细介绍)
5、如果你用应用程序比较大,希望UI和业务能够分开维护

import React, { useReducer, useEffect } from "react";
import ReactDOM from "react-dom";

const initialState = {
  count: 0,
  step: 1
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: "tick" });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      <h1>{count}</h1>
      <input
        value={step}
        onChange={(e) => {
          dispatch({
            type: "step",
            step: Number(e.target.value)
          });
        }}
      />
    </>
  );
}

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === "tick") {
    return { count: count + step, step };
  } else if (action.type === "step") {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}

4、useMemo 和 useCallback

useMemo 和 useCallback他们可以用来缓存函数、组件、变量,以避免两次渲染间的重复计算。

为什么使用 useMemo 和 useCallback

使用 memo 通常有三个原因: 1. ✅ 防止不必要的 effect。 2. ❗️防止不必要的 re-render。 3. ❗️防止不必要的重复计算。后两种优化往往被误用,导致出现大量的无效优化或冗余优化。

(1)防止不必要的 effect

当变量直接或者通过依赖链成为 useEffect 的依赖项时,那它可能需要被缓存。这是 useMemo 和 useCallback 最基本的用法。

// useMemo示例
const Component = () => {
  // 在 re-renders 之间缓存 a 的引用
  const a = useMemo(() => ({ test: 1 }), []);

  useEffect(() => {
    // 只有当 a 的值变化时,这里才会被触发
    doSomething();
  }, [a]);

  // the rest of the code
};

// useCallback 示例
const Component = () => {
  // 在 re-renders 之间缓存 fetch 函数
  const fetch = useCallback(() => {
    console.log('fetch some data here');
  }, []);

  useEffect(() => {
    // 仅fetch函数的值被改变时,这里才会被触发
    fetch();
  }, [fetch]);

  // the rest of the code

};
(2)防止不必要的 re-render
    1. 组件什么时候会 re-render
      三种情况组件会re-render: 1. 当本身的 props 或 state 改变时。 2. Context value 改变时,使用该值的组件会 re-render。 3. 当父组件重新渲染时,它所有的子组件都会 re-render,形成一条 re-render 链。
      存在的问题:第三个种 re-render 时机经常被开发者忽视,导致代码中存在大量的无效缓存。
const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
    // 无论 onClick 是否被缓存,Page 都会 re-render 
    <Page onClick={onClick} />
  );
};

当使用 setState 改变 state 时,App 会 re-render,作为子组件的 Page 也会跟着 re-render。这里 useCallback 是完全无效的,它并不能阻止 Page 的 re-render。

  • 2.如何防止子组件 re-render
    必须同时缓存 onClick 和组件本身,才能实现 Page 不触发 re-render
const PageMemoized = React.memo(Page);

const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  return (
   // useCallback结合memo一起才能控制子组件无效re-render 
    <PageMemoized onClick={onClick} />
  );
};

注意下面这种情况,由于 value 会随着 App 的 re-render 重新定义,引用值发生变化,导致 PageMemoized 仍然会触发 re-render。

const PageMemoized = memo(({value, onClick}) => {
  console.log(value); // 触发父组件的handleBtn方法时,仍然会打印value值,去掉value是常亮活着是memoized类型数据时,触发handleBtn不会打印value
  return <div onClick={onClick}>value的长度:{value?.length}</div>
});

const App = () => {
  const [state, setState] = useState(1);

  const onClick = useCallback(() => {
    console.log('Do something on click');
  }, []);

  const handleBtn = () => {
    setState(state + 1);
  }

  return (
    <>
    <div onClick={handleBtn}>点击按钮</div>
    <span>{state}</span>
    // PageMemoized还是会re-render,因为value不是一个memoized, 使用useMemo处理useMemo(() => [1, 2, 3], [])后,handleBtn触发时子组件console里不会再打印。
    <PageMemoized onClick={onClick} value={[1, 2, 3]} />
    </>
  );
};

现在可以得出结论,必须同时满足以下两个条件,子组件才不会 re-render: 1. 子组件自身被缓存。 2. 子组件所有的 prop 都是memoized。

(3)防止不必要的重复计算

useMemo 的基本作用是,缓存计算结果,避免在每次渲染时都进行高开销的计算。
实际上,组件渲染才是性能的瓶颈,应该把 useMemo 用在程序里渲染昂贵的组件上,而不是数值计算上。当然,除非这个计算真的很昂贵,比如阶乘计算。
至于为什么不给所有的组件都使用 useMemo,useMemo 是有成本的,它会增加整体程序初始化的耗时,并不适合全局全面使用,它更适合做局部的优化。

function App(props) {
  const start = props.start;
  const list = props.list;
  const fibValue = useMemo(() => fibonacci(start), [start]); // 缓存耗时操作
  // const MemoList = useMemo(() => <List list={list} />, [list]); 

  return (
    <>
      <div>Do some expensive calculation: {fibValue}</div>
      {MemoList}
      <Other />
    </>
  );
}

// 只有列表项改变时组件才会re-render
const MemoList = React.memo(({ list }) => {
  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>{item.content}</li>
      ))}
    </ul>
  );
});

相比React.memo,useMemo在组件内部调用,可以访问组件的props和state,所以它拥有更细粒度的依赖控制。

5、useRef

  • 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
  • 返回的 ref 对象在组件的整个生命周期内保持不变
  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
  • 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
  • useRef 类似于类组件的 this。

tips: 欢迎各位指正!
参考:
1.How to useMemo and useCallback: you can remove most of them
2.如何正确使用 useMemo 和 useCallback
3.A Complete Guide to useEffect
4.React Hooks完全上手指南

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

推荐阅读更多精彩内容

  • 大纲 😁 函数式编程🏆 什么是纯函数🏆 什么是副作用(Effect)🏆 为什么要使用纯函数 😁 React函数组件...
    这个前端不太冷阅读 895评论 0 1
  • 1 关于hook 1.1 为什么使用hook 在react类组件(class)写法中,有setState和生命周期...
    江湖yi山人阅读 1,833评论 0 3
  • React Hooks Hook是React v16.8的新特性,可以用函数的形式代替原来的继承类的形式,可以在不...
    左冬的博客阅读 755评论 0 1
  • 1.关于 React Hooks React 提倡函数式编程,view = fn(props),函数更灵活,更易拆...
    轻言duyx阅读 1,091评论 0 1
  • 什么是hooks? hooks 是 react 在16.8版本开始引入的一个新功能,它扩展了函数组件的功能,使得函...
    JoeRay61阅读 549评论 0 0