Hooks API

useEffect

// deps 是依赖数组,可以为空 
useEffect(() => {
  console.log('useEffect')
},  [deps]);

useEffect的使用 参考上篇文章

执行时机:

  • useEffect可以看做componentDidMount、componentDidUpdate、componentWillUnmount生命周期的结合。
  • 只有当deps里的变量发生改变时,才会执行。当第二参数是空数组,该effect 只会在组件didMount和willUnmount后执行;当第二个参数为空时,组件每次渲染都会执行。

useMemo

// 返回一个有记忆的值(memoized)
const memoizedValue = useMemo(() => computeExpensiveValue(deps),deps);

// useMemo 可以帮助避免子组件的不必要的rerender

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <div>
      {child1}
      {child2}
    </div>
  )
}

执行时机:

  • useMemo组件rendering 的时候执行,在useEffect之前。因此副作用应放在useEffect里,而不是useMemo。
  • 只有当deps发生变化时,useMemo 才会重新计算memoizedValue, 避免渲染时不必要的计算。当第二个参数为空时,组件每次渲染都会执行。

useCallback

// 返回一个有记忆的callback
const memoizedCallback = useCallback(
  () => {
    doSomething(deps);
  },
  [deps],
);

执行时机:

  • 只有当deps发生变化时,memoizedCallback才会改变。这对通过传递callbacks来优化子组件的渲染很有帮助。(配合子组件的shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用)
// useCallback 与useMemo
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

useCallback 的真正目的还是在于缓存了每次渲染时 inline callback 的实例,这样方便配合子组件的 shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用。

useCallback 适用于所有的场景吗?
我们知道useCallback也是有依赖项的,如果一个 callback 依赖于一个经常变化的 state,这个 callback 的引用是无法缓存的。React 文档的 FAQ 里也提到了这个问题,还原一下问题的场景:

function Form() {
  const [text, setText] = useState('');

  const handleSubmit = useCallback(() => {
    console.log(text);
  }, [text]); // 每次 text 变化时 handleSubmit 都会变

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} /> // 很重的组件
    </div>
  );
}

官网提出,如果要记忆化的callback函数是一个事件处理器,rendering时不需要调用,那么,你可以用useRef创建一个实例变量,手动把最新的值存进来。例如:

function Form() {
  const [text, updateText] = useState('');
  const textRef = useRef();

  useEffect(() => {
    textRef.current = text; // Write it to the ref
  });

  const handleSubmit = useCallback(() => {
    const currentText = textRef.current; // Read it from the ref
    alert(currentText);
  }, [textRef]); // Don't recreate handleSubmit like [text] would do

  return (
    <div>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </div>
  );
}

但是,上面的额解决方案也有缺陷,只有在 DOM 更新时才对 ref.current 做更新,会导致在 render 阶段不能调用这个函数。更严重的是,因为对 ref 做了修改,在未来的 React 异步模式下可能会有诡异的情况出现(因此上文中官方的解法也是”异步模式不安全“的)。

怎么避免深度传递callback?

考虑到上面提到的弊端,目前推荐的解决方案是使用useReducer,传递dispatch通过context。reducer 其实是在下次 render 时才执行的,所以在 reducer 里,访问到的永远是新的 props 和 state。

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // useReducer 返回的 dispatch 函数是自带 memoize 的
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

Any child in the tree inside TodosApp can use the dispatch function to pass actions up to TodosApp:

function DeepChild(props) {
  // If we want to perform an action, we can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

TodosApp的任何一个子组件都可以使用dispatch函数向TodosApp传递actions.

如果你想同时把 state 作为 context 传递下去,请分成两个 context 来声明。

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

推荐阅读更多精彩内容