前端视界:图解React

806 阅读8分钟

前端视界:图解React

本文从浅入深的全局解析 React,主要介绍其基础概念版本演进生命周期Hooks生态链工程化性能优化核心API核心原理部分源码解析

引言

最近失业,想着把我了解的一些前端技术整理一下,写了《前端视界:我的技术与思考》这篇文章,方便自己查阅,也供大家一起学习。

但之前的文章,技术广度是有了,但深度不够,所以我想分析某个技术或方向,从浅入深的分析其基础概念、使用 API 到核心原理及部分源码解析。

今天,给大家带来一篇图解 React 的文章,从浅入深的全局解析 React,主要介绍其基础概念版本演进生命周期Hooks生态链工程化性能优化核心API核心原理部分源码解析

React的全局图解

react

React 的全局概念和生态是非常丰富的,那解析 React 就要从多个方面来解析。

  • 基础概念:介绍什么是 React诞生背景重要性应用场景版本演进

  • 版本演进:介绍 React 1516171819 版本的主要特性和变化。

  • 生命周期:介绍括组件的挂载更新卸载等阶段,以及常用的生命周期方法。

  • Hooks:介绍 useStateuseEffectuseContext 等 hooks 方法,用于在函数组件中管理状态和副作用。

  • 生态链:包括 UI 组件库路由库状态管理库等,用于快速构建应用程序。

  • 工程化:包括构建工具模块打包测试编写等,用于提高开发效率和代码质量。

  • 性能优化:包括代码分割懒加载Memoization性能监测分析工具等,用于提高应用程序的性能。

  • 核心API:介绍 React APIReactDOM APIReactDOMServer API等,用于与 DOM 进行交互和测试。

  • 核心原理:包括 单向数据流虚拟DOMJSXFiber架构Diff算法、事件系统等,用于实现 React 的核心功能。

  • 部分源码解析:包括 ReactDOM 的 renderupdatecommit 等方法的实现,以及 Fiber 架构的核心算法。

React基础概念

react-1

React 是一个由 Meta(Facebook) 开发并开源的、用于构建用户界面的 JS 库。它的核心思想是通过组件化的方式来构建可重用的 UI 元素,常用于构建大型、复杂的前端应用程序。具有虚拟DOM、组件化、声明式编程和丰富的生态系统:库、工具和活跃的社区等。

因为其高性能组件化开发效率高跨平台等特点,常用场景包括:大型复杂的web应用、SPA单页应用、移动应用开发(RN)、SSR服务端渲染(NextJs)、桌面应用开发(Electron)等。

React版本演进

react-2

React 的版本迭代非常频繁,从 0.1 到 19 版本(目前主流的版本覆盖在 15 到 18),React 不断地优化和改进,使其更加稳定、高效和易用。

React生命周期

react-3

如上图所示,React 有三个主要的生命周期阶段:挂载更新卸载,针对这三个阶段可以进行一些性能优化,在目前主流的开发流程中,直接使用类组件和生命周期函数已经不多,所以我们可以使用函数组件和 hooks 来代替。

但为了更好的理解生命周期执行过程,我们可以看下面一张图:

react16-lifeCycle

挂载阶段代码示例

constructor(props)

constructor(props) {
  super(props);
  this.state = { count: 0 };
}

static getDerivedStateFromProps(props, state):

static getDerivedStateFromProps(props, state) {
  if (props.initialCount !== state.count) {
    return { count: props.initialCount };
  }
  return null;
}

render():

render() {
  return <div>{this.state.count}</div>;
}

componentDidMount():

componentDidMount() {
  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => this.setState({ data }));
}

更新阶段代码示例

shouldComponentUpdate(nextProps, nextState):

shouldComponentUpdate(nextProps, nextState) {
  return nextState.count !== this.state.count;
}

getSnapshotBeforeUpdate(prevProps, prevState):

getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevProps.count !== this.props.count) {
    return this.someDOMElement.scrollHeight;
  }
  return null;
}

componentDidUpdate(prevProps, prevState, snapshot):

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot) {
    this.someDOMElement.scrollTop = this.someDOMElement.scrollHeight;
  }
}

卸载阶段代码示例

componentWillUnmount():

componentWillUnmount() {
  clearInterval(this.timer);
  this.someDOMElement.removeEventListener('click', this.handleClick);
}

性能优化

使用shouldComponentUpdate

在类组件中,通过实现此方法,可以控制组件是否需要重新渲染。如果返回 false,则跳过 render 和后续的生命周期方法。

shouldComponentUpdate(nextProps, nextState) {
  return nextState.count !== this.state.count;
}
使用React.memo

在函数组件中,React.memo 是一个高阶组件,用于缓存组件的渲染结果。如果 props 没有变化,则直接返回缓存的组件,避免重新渲染。

const MyComponent = React.memo(function MyComponent({ count }) {
  return <div>{count}</div>;
});
使用useMemo和useCallback

useMemo 用于缓存计算结果,useCallback 用于缓存函数引用,避免不必要渲染。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
合理使用useEffect

在函数组件中,useEffect用于执行副作用操作。通过合理设置依赖数组,可以避免不必要的副作用执行。

useEffect(() => {
  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => setData(data));
}, []); // 空数组表示仅在组件挂载和卸载时执行

React Hooks

react-4

如上所示,Hooks 是 React 16.8 版本引入的新特性,它允许在函数组件中使用状态和副作用。

useEffect代码示例

// useEffect 接收两个参数:一个包含副作用操作的函数,和一个依赖项数组
useEffect(() => {
  // 副作用操作:订阅或数据获取等
  document.title = `You clicked ${count} times`;

  // 可选的清理函数,用于在下次effect执行前或组件卸载时清理副作用
  return () => {
    // 清理操作,例如取消网络请求或移除事件监听器
  };
}, [count]); // 依赖项数组,当这些值变化时触发effect重新执行

useLayoutEffect代码示例

useLayoutEffect(() => {
  // 定义一个函数来获取 DOM 元素的尺寸
  执行xxx

  return () => {
    执行yyy
  };
}, []);

useContext代码示例

创建useContext

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

使用useContext

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemedButton() {
  const theme = useContext(ThemeContext); // 访问上下文中的值

  return (
    <button style={{ background: theme === 'dark' ? '#333' : '#ddd' }}>
      Click me
    </button>
  );
}

useReducer代码示例

定义 reducer 和 action 类型:

const initialState = { count: 0 };

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

使用 useReducer:

import React, { useReducer } from 'react';

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Current count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

useMemo代码示例

const heavyValue = useMemo(() => {
    // 模拟一个耗时计算
    let result = 0;
    for (let i = 0; i < 100000000; i++) {
      result += i;
    }
    return result;
  }, []); // 依赖数组为空,计算结果不会重新生成

useCallback代码示例

import React, { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // 依赖数组为空,函数不会重新创建

  return (
    <div>
      <p>Count: {count}</p>
      {/* ChildComponent 不会因为 handleClick 的变化而重新渲染 */}
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

function ChildComponent(props) {
  return <button onClick={props.onClick}>Click me</button>;
}

useRef代码示例

import React, { useRef } from 'react';

function InputFocus() {
  const inputRef = useRef(null); // 创建一个引用

  const focusInput = () => {
    inputRef.current.focus(); // 获取 DOM 元素并调用方法
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

useImperativeHandle代码示例

子组件使用

import React, { useRef, useImperativeHandle } from 'react';

function ChildComponent(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focusInput: () => {
      inputRef.current.focus();
    },
  }));

  return <input ref={inputRef} type="text" />;
}
export default React.forwardRef(ChildComponent);

父组件调用

function ParentComponent() {
  const childRef = useRef();

  const focusChildInput = () => {
    childRef.current.focusInput(); // 调用子组件暴露的方法
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={focusChildInput}>Focus Child Input</button>
    </div>
  );
}

自定义hooks - useCounter代码示例

自定义useCounter:

// 自定义 Hook: 管理计数器
const useCounter = (initialValue = 0) => {
  // 使用 useState 来存储当前计数
  const [count, setCount] = useState(initialValue);

  // 提供增加计数的方法
  const increment = () => setCount(count + 1);
  // 提供减少计数的方法
  const decrement = () => setCount(count - 1);
  // 提供重置计数的方法
  const reset = () => setCount(initialValue);

  return {
    count,
    increment,
    decrement,
    reset
  };
};

使用useCounter:

function Counter() {
  // 调用自定义 Hook,初始值为 0
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

React生态链

react-5

React 生态链非常丰富,包括UI组件库路由库状态管理库Next.jsReact Native 等。

React工程化和性能优化

react-6

选择 React 作为项目的开发框架时,那更看重的就是其在测试编译打包性能优化方面的优势,我们还是有必要了解下其的工程化和性能优化方面的知识。

React核心API

react-7

上图中,React APIReactDOM APIReactDOMServer API是 React 中最常用的 API,它们分别用于与 DOM 进行交互、与服务器端进行交互和在服务器端渲染 React 组件。

React核心原理

react-8

如上图所示,React 核心原理包括单向数据流虚拟DOMJSXFiber架构Diff算法事件系统等。

React部分源码解析

react-9

如上图所示,React 的源码解析涵盖了从初始化渲染到更新渲染的整个过程,包括 Fiber 数据结构的创建、组件的渲染和更新、事件处理等。通过深入理解这些源码,可以更好地掌握 React 的工作原理和核心机制,从而更有效地进行前端开发和优化。

但因为xmind里不太好插入代码段进行解析,下面我们逐步拆解上图的部分源码解析:

legacyRenderSubtreeIntoContainer 函数

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function
) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;

  if (!root) {
    // 如果容器未初始化过,调用 legacyCreateRootFromDOMContainer 创建根
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;

    // 如果有回调函数,调整其 this 指向
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function () {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }

    // 确保初始渲染尽快完成,不被打断
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function () {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // 更新操作
    updateContainer(children, fiberRoot, parentComponent, callback);
  }

  // 返回公共实例
  return getPublicRootInstance(fiberRoot);
}

解析

  • 检查容器是否已初始化:通过 container._reactRootContainer 判断容器是否已初始化。

  • 如果是初始渲染,调用 legacyCreateRootFromDOMContainer 创建根。

  • 调整回调函数的 this 指向,使其指向真实的 DOM 对象。

  • 通过 unbatchedUpdates 确保初始渲染尽快完成,不被打断。

legacyCreateRootFromDOMContainer 函数

function legacyCreateRootFromDOMContainer(container: Container, forceHydrate: boolean): RootType {
  const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);

  // 如果不是服务器端渲染,清空容器中的节点
  if (!shouldHydrate) {
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }

  // 创建 LegacyRoot
  return createLegacyRoot(container, shouldHydrate ? {hydrate: true} : undefined);
}

解析

  • 判断是否为服务器端渲染,通过 forceHydrate 参数和 shouldHydrateDueToLegacyHeuristic 函数来判断。

  • 如果不是服务器端渲染,清空容器中的节点

  • 调用 createLegacyRoot 创建 LegacyRoot

createLegacyRoot 函数

export function createLegacyRoot(container: Container, options?: RootOptions): RootType {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

解析

  • 通过 new ReactDOMBlockingRoot(container, LegacyRoot, options) 创建 ReactDOMBlockingRoot 实例。

  • 在实例化过程中,调用 createRootImpl 创建根 Fiber 数据结构

createRootImpl 函数

function createRootImpl(container: Container, rootType: RootType, options: RootOptions): FiberRoot {
  // 调用 createFiberRoot 创建容器对象 FiberRootNode
  const fiberRoot = createFiberRoot(container, rootType, options);

  // markContainerAsRoot 将容器标记为根
  markContainerAsRoot(rootType, container);

  // 返回创建好的根 Fiber 数据结构
  return fiberRoot;
}

解析

  • 调用 createFiberRoot 创建容器对象 FiberRootNode

  • markContainerAsRoot 将容器标记为根。

  • 返回创建好的根 Fiber 数据结构。

createContainer 函数

function createFiberRoot(
  container: Container,
  rootType: RootType,
  options: RootOptions
): FiberRoot {
  // 初始化 FiberRoot 对象,并设置其相关属性,如 container(容器)、tag(LegacyRoot 标记)、hydrate(是否为服务器端渲染)等。
  const fiberRoot: FiberRoot = {
    container,
    tag: LegacyRoot,
    current: null,
    finishedWork: null,
    context: null,
    pendingContext: null,
    hydrate: options?.hydrate || false,
    //...
  };

  return fiberRoot;
}

解析

  • 初始化 FiberRoot 对象,并设置其相关属性,如 container(容器)、tag(LegacyRoot 标记)hydrate(是否为服务器端渲染)等。

reconcileSingleTextNode 函数

function reconcileSingleTextNode(returnFiber: Fiber, existingChild: Node, textContent: string) {
  // 如果存在现有子节点且是文本节点,更新文本内容
  if (existingChild.nodeType === TEXT_NODE) {
    existingChild.nodeValue = textContent;
    return existingChild;
  }
  // 否则创建新的文本节点
  else {
    const textNode = document.createTextNode(textContent);
    return textNode;
  }
}

解析

  • 如果当前子节点是文本节点,则更新其文本内容。

  • 如果不是文本节点,则创建新的文本节点并返回。

reconcileSingleElement 函数

function reconcileSingleElement(
  returnFiber: Fiber,
  existingChild: Node,
  element: ReactElement
): Fiber {
  // 根据 React Element 创建 Fiber 对象
  const fiber = createFiberFromElement(element);
  // 设置 fiber 的父 Fiber 对象和 ref 属性。
  fiber.return = returnFiber;
  fiber.ref = element.ref;
  return fiber;
}

解析

  • 根据 React Element 创建 Fiber 对象。

  • 设置 fiber 的父 Fiber 对象和 ref 属性。

reconcileChildFibers 函数

function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: any
): Fiber {
  if (Array.isArray(newChildren)) {
    // 处理数组类型的子节点
    //...
  } else if (typeof newChildren === 'object' && newChildren !== null) {
    // 处理单个对象类型的子节点
    //...
  } else if (typeof newChildren === 'string' || typeof newChildren === 'number') {
    // 处理文本或数值类型的子节点
    //...
  }
  return null;
}

解析

  • 根据子节点的类型(数组、对象、文本或数值)进行相应的处理。

commitBeforeMutationEffects 函数

function commitBeforeMutationEffects(fiber: Fiber) {
  if (fiber.effectTag & Snapshot) {
    // 调用 getSnapshotBeforeUpdate 生命周期函数
    commitBeforeMutationEffectOnFiber(fiber);
  }
  if (fiber.child) {
    // 递归处理子节点
    commitBeforeMutationEffects(fiber.child);
  }
  if (fiber.sibling) {
    // 递归处理同级节点
    commitBeforeMutationEffects(fiber.sibling);
  }
}

解析

  • 循环 effect 链,获取每个需要 commitfiber 对象。

  • 如果 fiber 对象有 Snapshot 标记,则调用 commitBeforeMutationEffectOnFiber 函数。

commitBeforeMutationLifeCycles 函数

function commitBeforeMutationLifeCycles(fiber: Fiber) {
  // 判断 fiber 类型是否为类组件
  if (fiber.tag === ClassComponent) {
    const instance = fiber.stateNode;
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      // 如果是类组件且有 getSnapshotBeforeUpdate 生命周期函数,则调用该函数。
      instance.getSnapshotBeforeUpdate(fiber.memoizedProps, fiber.memoizedState);
    }
  }
}

解析

  • 判断 fiber 类型是否为类组件
  • 如果是类组件且有 getSnapshotBeforeUpdate 生命周期函数,则调用该函数。

commitMutationEffects 函数

function commitMutationEffects(fiber: Fiber) {
  if (fiber.effectTag & Placement) {
    // 执行 DOM 挂载操作
    commitPlacement(fiber);
  }
  if (fiber.effectTag & Update) {
    // 执行 DOM 更新操作
    commitUpdate(fiber);
  }
  if (fiber.child) {
    // 递归处理子节点
    commitMutationEffects(fiber.child);
  }
  if (fiber.sibling) {
    // 递归处理同级节点
    commitMutationEffects(fiber.sibling);
  }
}

解析

  • 循环 effect 链,获取每个需要 commit 的 fiber 对象。
  • 根据 fiber 对象的 effectTag 执行相应的 DOM 操作(如挂载更新等)。

前端视界专栏