「完整FX」前端跳槽突围课:React18底层源码深入剖析

35 阅读3分钟

前端跳槽突围课:React18底层源码深入剖析

//xia仔のke:3w ukoou com

useCallback、useMemo 分析 & 差别


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

根据官网文档的介绍我们可理解:在ab的变量值不变的情况下,memoizedCallback的引用不变。即:useCallback的第一个入参函数会被缓存,从而达到渲染性能优化的目的。


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

根据官方文档的介绍我们可理解:在ab的变量值不变的情况下,memoizedValue的值不变。即:useMemo函数的第一个入参函数不会被执行,从而达到节省计算量的目的。

分析

// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function App() {
  const [count, setCount] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const handleCount = () => setCount(count + 1);
  const handleTotal = () => setTotal(total + 1);
  const prevHandleCount = usePrevProps(handleCount);
  
  console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
  
  return (
    <div>
      <div>Count is {count}</div>
       <div>Total is {total}</div>
      <br/>
      <div>
        <button onClick={handleCount}>Increment Count</button>
        <button onClick={handleTotal}>Increment Total</button>
      </div>
    </div>
  )
}

ReactDOM.render(<App />, document.body)

我们重点看这一行

const handleCount = () => setCount(count + 1);

根据我们之前的理解,我们知道每次App组件渲染时这个handleCount都是重新创建的一个新函数。


const prevHandleCount = usePrevProps(handleCount);  
console.log('两次处理函数是否相等:', prevHandleCount === handleCount);

我们也可以通过比较上一次的prevHandleCount和本次的handleCount。可以明确的知道每次渲染时handleCount都是重新创建的一个新函数。

问题:它有什么问题呢?当我们将handleCount作为props传递给其他组件时会导致像PureComponentshouldComponentUpdateReact.memo等相关优化失效(因为每次都是不同的函数)

端跳槽突围课:React18底层源码深入剖析 - 实现Consumer

概念

首先要理解上下文(context)的作用以及提供者和消费者分别是什么,同时要思考这种模式解决的是什么问题(跨层级组件通信)。

context做的事情就是创建一个上下文对象,并且对外暴露提供者(通常在组件树中上层的位置)消费者,在上下文之内的所有子组件, 都可以访问这个上下文环境之内的数据,并且不用通过props。可以理解为有一个集中管理state的对象,并限定了这个对象可访问的范围, 在范围之内的子组件都能获取到它内部的值。

提供者为消费者提供context之内的数据,消费者获取提供者为它提供的数据,自然就解决了上边的问题。

用法

这里要用到一个小例子,功能就是主题颜色的切换。效果如图:

根据上边的概念和功能,分解一下要实现的步骤:

  • 创建一个上下文,来提供给我们提供者和消费者
  • 提供者提供数据
  • 消费者获取数据

这里的文件组织是这样的:

复制代码
├─context.js    // 存放context的文件
│─index.js      // 根组件,Provider所在的层级
│─Page.js       // 为了体现跨层级通信的添加的一个中间层级组件,子组件为Title和Paragraph
│─Title.js      // 消费者所在的层级
│─Paragraph.js  // 消费者所在的层级

创建一个上下文


import React from 'react'

const ThemeContext = React.createContext()

export const ThemeProvider = ThemeContext.Provider
export const ThemeConsumer = ThemeContext.Consumer

这里,ThemeContext就是一个被创建出来的上下文,它内部包含了两个属性,看名字就可以知道,一个是提供者一个是消费者。 Provider和Consumer是成对出现的,每一个Provider都会对应一个Consumer。而每一对都是由React.createContext()创建出来的。

page组件

没啥好说的,就是一个容器组件而已


const Page = () => <>
  <Title/>
  <Paragraph/>
</>

提供者提供数据

提供者一般位于比较上边的层级,ThemeProvider 接受的value就是它要提供的上下文对象。


// index.js
import { ThemeProvider } from './context'

render() {
  const { theme } = this.state
  return <ThemeProvider value={{ themeColor: theme }}>
    <Page/>
  </ThemeProvider>
}