FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

React hooks那些事儿 #248

Open FrankKai opened 3 years ago

FrankKai commented 3 years ago
FrankKai commented 3 years ago

useContext是什么意思?

context

context: react中的context无需为每层组件手动添加props,就可以在组件树之间传递数据的方法。

例如:地区偏好,UI主题,当前认证用户

const value = useContext(MyContext)

useContext

const value = useContext(MyContext)

这个value由距离当前组件最近的<MyContext.Provider>的value决定。当<MyContext.Provider>更新时,触发重新渲染,并使用最新传递给<MyContext.Provider>的value prop值。

使用了useContext的组件会在context值发生变化时重新渲染。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

更多关于React useContext的用法可以查看:React除了可以通过props传递数据以外,如何通过context方式传递数据?

useMemo是什么意思?

useMemo

const memoizedValue = useMemo(()=> computedExpensiveValue(a,b), [a,b])

memo(memoization)是什么意思? 记忆化。这是一种提高程序运行速度的优化技术,通过储存大计算量函数的返回值,当这个结果再次被需要时将其从缓存提取,从而不用再次节省计算时间。

传入一个“新建”函数和一个依赖数组。useMemo仅仅在其中一个依赖发生变化时重新计算记忆值。这个优化可以帮助避免每次渲染造成的昂贵计算

传入到useMemo的函数会在渲染时运行。不要在useMemo中做渲染时不想做的事。例如,side effects在useEffect这个钩子中,而不时在useMemo。

如果传入空数组,每次渲染都会有新值被计算。

可以将useMemo用于性能优化,而不是语义保证。 在未来,react可能会选择忘记之前的一些记忆值,在下一次渲染时重新计算他们,例如释放离线组件的缓存没有useMemo,代码也依然可以运行,然后再用useMemo做性能优化。

useCallback是什么意思?

这是一个React的hook。 返回一个memoized的callback。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

在[a,b]中的一个依赖发生变化时,回调触发。

useCallback(fn, deps)等价于useMemo(()=> fn, deps)

相比于vue而言,useCallback或者说useMemo可以类比为对象属性批量watch或者是对象属性deep watch

react的useCallback或者说useMemo,比vue的watch(单个),watch(deep)更加灵活。

useEffect与useCallback(useMemo)的区别是什么?

浏览器执行阶段:可见修改(DOM操作,动画,过渡)->样式规则计算->计算空间和位置->绘制像素内容->多个层合成 前四个阶段都是针对元素的,最后一个是针对层的。由点到面。 image

执行时间不同

useEffect在渲染完成后执行函数,更加准确的来说是在layout和paint完成之后。

The function passed to useEffect will run after the render is committed to the screen.Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint

useCallback(useMemo)在渲染过程中执行函数。

Remember that the function passed to useMemo runs during rendering.

哪些适合在渲染完成后执行,哪些适合在渲染过程中执行

渲染完成后执行:Mutations(DOM操作), subscriptions(订阅), timers, logging 渲染过程中执行:用于不依赖渲染完成的性能优化,状态一变更立即执行

一个例子阐明useEffect和useMemo的区别

useMemo最主要解决的问题:怎么在DOM改变的时候,控制某些函数不被触发。 例如下面这个例子,在name变更的时候,useEffect会在DOM渲染完成后出发price的函数,而useMemo可以精准的只触发更新name的函数。

这是一个非常非常好的例子,更加详细的博文在这里:useMemo和useEffect有什么区别?怎么使用useMemo

import React, {Fragment} from 'react'
import { useState, useEffect, useCallback, useMemo } from 'react'

const nameList = ['apple', 'peer', 'banana', 'lemon']
const Example = (props) => {
    const [price, setPrice] = useState(0)
    const [name, setName] = useState('apple')

    function getProductName() {
        console.log('getProductName触发')
        return name
    }
    // 只对name响应
    useEffect(() => {
        console.log('name effect 触发')
        getProductName()
    }, [name])

    // 只对price响应
    useEffect(() => {
        console.log('price effect 触发')
    }, [price])

    // memo化的getProductName函数   🧬🧬🧬
    const memo_getProductName = useMemo(() => {
        console.log('name memo 触发')
        return () => name  // 返回一个函数
    }, [name])

    return (
        <Fragment>
            <p>{name}</p>
            <p>{price}</p>
            <p>普通的name:{getProductName()}</p>
            <p>memo化的:{memo_getProductName ()}</p>
            <button onClick={() => setPrice(price+1)}>价钱+1</button>
            <button onClick={() => setName(nameList[Math.random() * nameList.length << 0])}>修改名字</button>
        </Fragment>
    )
}
export default Example

点击价钱+1按钮(通过useMemo,多余的memo_getProductName ()没有被触发,只触发price相关的函数)

getProductName触发 price effect 触发

点击修改名字按钮(通过useEffect,只触发name相关)

name memo 触发 getProductName触发 name effect 触发 getProductName触发

总结

useEffect面对一些依赖于某个state的DOM渲染时,会出现一些性能问题,而useMemo可以优化这个问题。 最后,用一句话来概括useMemo的话,那就是:useMemo可以避免一些useEffect搞不定的不必要的重复渲染和重复执行问题。

FrankKai commented 3 years ago

useRef如何解决空指针问题?

通常来说,useRef用于引用组件的Dom节点。Vue中的ref则是引用一个vue组件。与Vue不同,react中的ref不仅仅是引用Dom节点,还可以生成一个内存不变的对象引用。

使用useState导致的空指针示例

const [foo, setFoo] = useState(null);

const handler = () => {
    setFoo("hello")
}

useEffect(() => {
    return () => {
      // 无论怎样foo都是null,给useEffect的deps加入foo也不行
      if (foo === "hello") {
          // do something...
      }
    }
}, [])

使用useRef的正确示例(解决事件处理器中对象为null的问题)

const foo = useRef(null)

const handler = () => {
    foo.current = "hello"
}

useEffect(() => {

    return () => {
      // foo.current为hello
      if (foo.current === "hello") {
          // do something...
      }
    }
}, [])

useRef解决空指针问题的原因是什么?

总结起来就是:useRef生成的对象,在组件生命周期期间内存地址都是不变的。

const refContainer = useRef(initialValue);

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.

总结一下会使用到useRef解决空指针问题的场景:

FrankKai commented 3 years ago

如何通过useReducer实现forceUpdate的效果?

首先来看看useReducer的使用场景

再来看看为什么要用useReducer实现forceUpdate的效果?

如果某些情况下state没有发生变化的话,可以使用useReducer去强制重新渲染。

 const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }