React

作者: shaokang 时间: February 23, 2022字数:4088

组件基础

事件机制

React 并不是将 click 事件绑定到真实 DOM 上,而是在 document 处监听所有事件,当事件发生并冒泡到 document 处,React 将事件内容封装并交由真正的处理函数运行 — 顶层事件代理机制。
这样做的好处是不仅能减少内存的消耗、提高性能,还能在组件挂载和销毁时统一订阅和移除事件。

除此之外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件。因此如果要阻止事件冒泡的话,需要调用 event.preventDefault 方法而不是 event.stopPropagation。
这样做的目的是:

  1. [兼容浏览器,跨平台] 合成事件能抹平浏览器间的兼容问题,另外这是跨浏览的原生事件包装器,赋予了跨浏览器开发的能力。
  2. [垃圾回收] 对原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果有多个事件监听,就需要分配多个事件对象,造成高额的内存分配问题。对合成事件而言,有专门的事件池来管理事件的创建和销毁,能够更好的复用和销毁。
  3. [方便管理] 便于 React 统一管理和事务机制。

事件顺序:原生事件执行,合成事件执行,document 上监听的事件。

React 高阶组件、Render props、hooks 有什么区别

这三者是目前 react 解决代码复用的主要方式:

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。

优点 ∶ 逻辑服用、不影响被包裹组件的内部逻辑。 缺点 ∶

  • 名称冲突:不同的 HOC 可能会给组件添加相同的 props,这就可能会导致冲突。这种问题可以通过约定命名规范来避免,但是这种方式并不完美。

    假设你有两个 HOCs,一个是 withUser,一个是 withNetwork。他们都会向被包装的组件传递一个名为 data 的 prop。

    // withUser HOC
    function withUser(Component) {
        return function WrappedComponent(props) {
            const user = { name: 'John Doe' };
            return <Component {...props} data={user} />;
        };
    }
    
    // withNetwork HOC
    function withNetwork(Component) {
        return function WrappedComponent(props) {
            const network = { status: 'connected' };
            return <Component {...props} data={network} />;
        };
    }
    

    然后你有一个 Profile 组件,你希望这个组件同时具有 withUser 和 withNetwork 的功能。

    function Profile(props) {
        const { data } = props;
        return <p>{JSON.stringify(data)}</p>;
    }
    const EnhancedProfile = withUser(withNetwork(Profile));
    

    在这里,withUser 和 withNetwork 都试图向 Profile 组件传递 data prop,但是由于他们使用了相同的名称,所以实际上 Profile 组件接收到的 data 只会是其中一个 HOC 提供的数据,另一个 HOC 提供的数据会被覆盖。这就是所谓的名称冲突。

  • 不透明:HOC 在组件之间创建了一个抽象层,这使得类型信息更难以理解和追踪。此外,当我们使用 HOC 包装组件时,新的复合组件的属性会被原始组件继承,这可能会导致属性冲突。

    // withLoading HOC
    function withLoading(Component) {
        return function WrappedComponent(props) {
            const extraProps = { message: 'Loading...' };
            return <Component {...props} {...extraProps} />;
        };
    }
    
    // withError HOC
    function withError(Component) {
        return function WrappedComponent(props) {
            const extraProps = { message: 'Error!' };
            return <Component {...props} {...extraProps} />;
        };
    }
    
    function DataDisplay(props) {
        return <p>{props.message}</p>;
    }
    
    const EnhancedDataDisplay = withLoading(withError(DataDisplay));
    

    在这个例子中,”属性冲突”的问题就出现了。withLoading 和 withError 都向 DataDisplay 组件添加了 message 属性,但是由于 withLoading 是后添加的,所以 DataDisplay 组件实际上看到的 message 是 ‘Loading…’,而不是 ‘Error!’。这就是属性冲突。

  • refs 必须使用 React 提供的其他方式,如 React.forwardRef

render props 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

优点:数据共享、代码复用,将组件内的 state 作为 props 传递给调用者,将渲染逻辑交给调用者。 缺点:

  • 无法在 return 语句外访问数据
  • 嵌套深度:当使用多个 render props 时,组件树的嵌套深度可能会增加,这可能导致代码难以阅读和理解。这个问题也被称为“回调地狱”。
  • 性能问题:每次渲染都会创建一个新的函数,这可能会导致子组件无谓的重渲染,从而影响性能。

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义 hook,可以复用代码逻辑。

hook 的优点如下 ∶

  • 使用直观;
  • 解决 hoc 的 prop 重名问题;
  • 解决 render props 因共享数据 而出现嵌套地狱的问题;
  • 能在 return 之外使用数据的问题。

React Hooks 解决了什么问题

React Hooks 是 React 16.8 引入的一项新特性,它解决了以下几个问题:

  • 在组件之间复用状态逻辑:在使用类组件时,对于复用一些状态逻辑,需要使用高阶组件或者 render props 等方式,代码会变得复杂和冗余。而使用 Hooks 可以通过自定义的 Hook 来复用状态逻辑,将相关的状态和操作都封装在一个函数中,以便在不同的组件中进行复用。
  • 处理组件之间的副作用:在类组件中,副作用的处理通常在生命周期方法中进行,例如 componentDidMount、componentDidUpdate、componentWillUnmount 等。这样会导致相关的逻辑被分散在不同的生命周期方法中,不易于维护和理解。而使用 useEffect Hook 可以将组件的副作用逻辑集中在一起,使代码更加清晰和易于管理。
  • 函数组件中使用状态:在 React 16.7 之前,函数组件是无状态的,无法使用类组件中的状态(state)。而使用 useState Hook 可以在函数组件中使用状态,使函数组件具备了类组件的状态管理能力。
  • 简化 React 组件的编写:使用 Hooks 可以让组件的编写更加简洁和直观,不需要关注类组件中的繁琐的生命周期方法,也不需要使用 this 关键字。同时,使用函数组件代替类组件,可以减少代码量和提高性能。

总的来说,React Hooks 的出现使得组件的编写更加简单和直观,使得状态管理和副作用处理更加灵活和方便,提高了开发效率和代码的可维护性。但需要注意,Hooks 是在 React 16.8 引入的,如果项目使用的是旧版本的 React,就无法使用 Hooks。

React 16 架构

  • Scheduler(调度器) – 调度任务的优先级,高优任务优先进入 Reconciler
  • Reconciler(协调器) – 负责找出变化的组件
  • Renderer(渲染器) – 负责将变化的组件渲染到页面上

Scheduler
当浏览器有剩余时间通知,类似 requestIdleCallback 机制

Reconciler 递归任务可中断,shouldYield 判断当前是否有剩余时间。对虚拟 DOM 打上增/删/更新的标记。

Renderer 根据 Reconciler 为虚拟 DOM 打的标记,同步执行对应的 DOM 操作。