react hooks实现原理解析

目录
  • react hooks 实现
    • Hooks 解决了什么问题
  • Hooks API 类型
    • 首先接触到的是 State hooks
    • 其次接触到的是 Effect hooks
    • 最后接触到的是 custom hooks
  • Hooks 实现方式
    • 问题一:useState dispatch 函数如何与其使用的 Function Component 进行绑定

react hooks 实现

Hooks 解决了什么问题

React 的设计哲学中,简单的来说可以用下面这条公式来表示:

UI = f(data)

等号的左边时 UI 代表的最终画出来的界面;等号的右边是一个函数,也就是我们写的 React 相关的代码;data 就是数据,在 React 中,data 可以是 state 或者 props。
UI 就是把 data 作为参数传递给 f 运算出来的结果。这个公式的含义就是,如果要渲染界面,不要直接去操纵 DOM 元素,而是修改数据,由数据去驱动 React 来修改界面。
我们开发者要做的,就是设计出合理的数据模型,让我们的代码完全根据数据来描述界面应该画成什么样子,而不必纠结如何去操作浏览器中的 DOM 树结构。

总体的设计原则:

  • 界面完全由数据驱动
  • 一切皆组件
  • 使用 props 进行组件之间通讯

与之带来的问题有哪些呢?

  • 组件之间数据交流耦合度过高,许多组件之间需要共享的数据需要层层的传递;传统的解决方式呢!

    • 变量提升
    • 高阶函数透传
    • 引入第三方数据管理库,redux、mobx
    • 以上三种设计方式都是,都是将数据提升至父节点或者最高节点,然后数据层层传递
  • ClassComponet 生命周期的学习成本,以及强关联的代码逻辑由于生命周期钩子函数的执行过程,需要将代码进行强行拆分;常见的:
class SomeCompoent extends Component {

  componetDidMount() {
    const node = this.refs['myRef'];
    node.addEventListener('mouseDown', handlerMouseDown);
    node.addEventListener('mouseUp', handlerMouseUp)
  }

  ...

  componetWillunmount() {
    const node = this.refs['myRef'];
    node.removeEventListener('mouseDown', handlerMouseDown)
    node.removeEventListener('mouseUp', handlerMouseUp)
  }
}

可以说 Hooks 的出现上面的问题都会迎刃而解

Hooks API 类型

据官方声明,hooks 是完全向后兼容的,class componet 不会被移除,作为开发者可以慢慢迁移到最新的 API。

Hooks 主要分三种:

  • State hooks : 可以让 function componet 使用 state
  • Effect hooks : 可以让 function componet 使用生命周期和 side effect
  • Custom hooks: 根据 react 提供的 useState、useReducer、useEffect、useRef等自定义自己需要的 hooks

下面我们来了解一下 Hooks。

首先接触到的是 State hooks

useState 是我们第一个接触到 React Hooks,其主要作用是让 Function Component 可以使用 state,接受一个参数做为 state 的初始值,返回当前的 state 和 dispatch。

import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>        Click me      </button>
    </div>
  );
}

其中 useState 可以多次声明;

function FunctionalComponent () {
  const [state1, setState1] = useState(1)
  const [state2, setState2] = useState(2)
  const [state3, setState3] = useState(3)

  return <div>{state1}{...}</div>
}

与之对应的 hooks 还有 useReducer,如果是一个状态对应不同类型更新处理,则可以使用 useReducer。

其次接触到的是 Effect hooks

useEffect 的使用是让 Function Componet 组件具备 life-cycles 声明周期函数;比如 componetDidMount、componetDidUpdate、shouldCompoentUpdate 以及 componetWiillunmount 都集中在这一个函数中执行,叫 useEffect。这个函数有点类似 Redux 的 subscribe,会在每次 props、state 触发 render 之后执行。(在组件第一次 render和每次 update 后触发)。

为什么叫 useEffect 呢?官方的解释:因为我们通常在生命周期内做很多操作都会产生一些 side-effect (副作用) 的操作,比如更新 DOM,fetch 数据等。

useEffect 是使用:

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

function useMousemove() {
    const [client, setClient] = useState({x: 0, y: 0});

  useEffect(() => {

    const handlerMouseCallback = (e) => {
        setClient({
          x: e.clientX,
        y: e.clientY
      })
    };
    // 在组件首次 render 之后, 既在 didMount 时调用
      document.addEventListener('mousemove', handlerMouseCallback, false);

    return () => {
      // 在组件卸载之后执行
        document.removeEventListener('mousemove', handlerMouseCallback, false);
    }
  })

  return client;
}

其中 useEffect 只是在组件首次 render 之后即 didMount 之后调用的,以及在组件卸载之时即 unmount 之后调用,如果需要在 DOM 更新之后同步执行,可以使用 useLayoutEffect。

最后接触到的是 custom hooks

根据官方提供的 useXXX API 结合自己的业务场景,可以使用自定义开发需要的 custom hooks,从而抽离业务开发数据,按需引入;实现业务数据与视图数据的充分解耦。

Hooks 实现方式

在上面的基础之后,对于 hooks 的使用应该有了基本的了解,下面我们结合 hooks 源码对于 hooks 如何能保存无状态组件的原理进行剥离。

Hooks 源码在 Reactreact-reconclier** 中的 ReactFiberHooks.js ,代码有 600 行,理解起来也是很方便的

Hooks 的基本类型:

type Hooks = {
    memoizedState: any, // 指向当前渲染节点 Fiber
  baseState: any, // 初始化 initialState, 已经每次 dispatch 之后 newState
  baseUpdate: Update<any> | null,// 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
  queue: UpdateQueue<any> | null,// UpdateQueue 通过
  next: Hook | null, // link 到下一个 hooks,通过 next 串联每一 hooks
}

type Effect = {
  tag: HookEffectTag, // effectTag 标记当前 hook 作用在 life-cycles 的哪一个阶段
  create: () => mixed, // 初始化 callback
  destroy: (() => mixed) | null, // 卸载 callback
  deps: Array<mixed> | null,
  next: Effect, // 同上
};

React Hooks 全局维护了一个 workInProgressHook 变量,每一次调取 Hooks API 都会首先调取 createWorkInProgressHooks 函数。参考React实战视频讲解:进入学习

function createWorkInProgressHook() {
  if (workInProgressHook === null) {
    // This is the first hook in the list
    if (firstWorkInProgressHook === null) {
      currentHook = firstCurrentHook;
      if (currentHook === null) {
        // This is a newly mounted hook
        workInProgressHook = createHook();
      } else {
        // Clone the current hook.        workInProgressHook = cloneHook(currentHook);
      }
      firstWorkInProgressHook = workInProgressHook;
    } else {
      // There's already a work-in-progress. Reuse it.      currentHook = firstCurrentHook;
      workInProgressHook = firstWorkInProgressHook;
    }
  } else {
    if (workInProgressHook.next === null) {
      let hook;
      if (currentHook === null) {
        // This is a newly mounted hook
        hook = createHook();
      } else {
        currentHook = currentHook.next;
        if (currentHook === null) {
          // This is a newly mounted hook
          hook = createHook();
        } else {
          // Clone the current hook.          hook = cloneHook(currentHook);
        }
      }
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    } else {
      // There's already a work-in-progress. Reuse it.      workInProgressHook = workInProgressHook.next;
      currentHook = currentHook !== null ? currentHook.next : null;
    }
  }
  return workInProgressHook;
}

假设我们需要执行以下 hooks 代码:

function FunctionComponet() {

  const [ state0, setState0 ] = useState(0);
  const [ state1, setState1 ] = useState(1);
  useEffect(() => {
      document.addEventListener('mousemove', handlerMouseMove, false);
    ...
    ...
    ...
    return () => {
      ...
      ...
      ...
        document.removeEventListener('mousemove', handlerMouseMove, false);
    }
  })

  const [ satte3, setState3 ] = useState(3);
  return [state0, state1, state3];
}

当我们了解 React Hooks 的简单原理,得到 Hooks 的串联不是一个数组,但是是一个链式的数据结构,从根节点 workInProgressHook 向下通过 next 进行串联。这也就是为什么 Hooks 不能嵌套使用,不能在条件判断中使用,不能在循环中使用。否则会破坏链式结构。

问题一:useState dispatch 函数如何与其使用的 Function Component 进行绑定

下面我们先看一段代码:

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const useWindowSize = () => {
    let [size, setSize] = useState([window.innerWidth, window.innerHeight])
    useEffect(() => {
        let handleWindowResize = event => {
            setSize([window.innerWidth, window.innerHeight])
        }
        window.addEventListener('resize', handleWindowResize)
        return () => window.removeEventListener('resize', handleWindowResize)
    }, [])
    return size
}

const App = () => {
    const [ innerWidth, innerHeight ] = useWindowSize();
  return (
    <ul>
          <li>innerWidth: {innerWidth}</li>
            <li>innerHeight: {innerHeight}</li>
    </ul>
  )
}

ReactDOM.render(<App/>, document.getElementById('root'))

useState 的作用是让 Function Component 具备 State 的能力,但是对于开发者来讲,只要在 Function Component 中引入了 hooks 函数,dispatch 之后就能够作用就能准确的作用在当前的组件上,不经意会有此疑问,带着这个疑问,阅读一下源码。

function useState(initialState){  return useReducer(
    basicStateReducer,    // useReducer has a special case to support lazy useState initializers    (initialState: any),  );}function useReducer(reducer, initialState, initialAction) {  // 解析当前正在 rendering 的 Fiber
    let fiber = (currentlyRenderingFiber = resolveCurrentlyRenderingFiber());  workInProgressHook = createWorkInProgressHook();  // 此处省略部分源码  ...  ...  ...  // dispathAction 会绑定当前真在渲染的 Fiber, 重点在 dispatchAction 中  const dispatch = dispatchAction.bind(null, currentlyRenderingFiber,queue,)  return [workInProgressHook.memoizedState, dispatch];}function dispatchAction(fiber, queue, action) {    const alternate = fiber.alternate;  const update: Update<S, A> = {    expirationTime,    action,    eagerReducer: null,    eagerState: null,    next: null,  };  ......  ......  ......  scheduleWork(fiber, expirationTime);}

到此这篇关于react hooks实现原理的文章就介绍到这了,更多相关react hooks原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • react的hooks的用法详解

    hooks的作用 它改变了原始的React类的开发方式,改用了函数形式;它改变了复杂的状态操作形式,让程序员用起来更轻松;它改变了一个状态组件的复用性,让组件的复用性大大增加. useState // 声明状态 const [ count , setCount ] = useState(0); // 使用状态 <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>cli

  • React hooks的优缺点详解

    前言 Hook 是 React 16.8 的新增特性.它是完全可选的,并且100%向后兼容.它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态.生命周期钩子等.从概念上讲,React 组件一直更像是函数.而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则. 优点: 1.代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护.例如,每个生

  • React 使用Hooks简化受控组件的状态绑定

    开始之前 阅读本文需要对以下几项有一定了解 ECMAScript 6 文章中大量用到了 ES6 语法,比如解构赋值和函数参数默认值.剩余参数.展开语法.箭头函数等. Hooks React 在 16.8 版本中推出了 Hooks,它允许你在"函数组件"中使用"类组件"的一些特性. React 本身提供了一些 Hooks,比如 useState.useReducer 等.通过在一个以"use"作为命名起始的函数中调用这些 Hooks,就得到了一个

  • 详解react hooks组件间的传值方式(使用ts)

    目录 父传子 子传父 跨级组件(父传后代) 父传子 通过props传值,使用useState来控制state的状态值 父组件 Father.tsx里: 子组件 Child.tsx里: 展示效果: 子传父 跟react的方式一样,像子组件传入回调函数,通过接收子组件的返回值,再去更新父组件的state 父组件,Father.tsx里: 子组件,Child.tsx里: 展示效果: 子传父优化版,使用useCallback存放处理事件的函数 父组件,Father.tsx里: 子组件,Child.tsx

  • React之Hooks详解

    目录 什么是钩子(hooks) 类组件 函数组件 为什么创造Hooks 总结 什么是钩子(hooks) 消息处理的一种方法, 用来监视指定程序 函数组件中需要处理副作用,可以用钩子把外部代码"钩"进来 常用钩子:useState, useEffect, useContext, useReducer Hooks一律使用use前缀命名:useXXX 类组件 函数组件 一类特殊的函数,为你的函数式组件注入特殊的功能 为什么创造Hooks 有些类组件冗长且复杂,难以复用 结局方案:无状态组件与

  • 教你在react中创建自定义hooks

    一.什么是自定义hooks 逻辑复用 简单来说就是使用自定义hook可以将某些组件逻辑提取到可重用的函数中. 自定义hook是一个从use开始的调用其他hook的Javascript函数. 二.不使用自定义hook时 例1:当我们整个页面需要获取用户鼠标移动的坐标时,不使用hook的代码,我们可以这样写 const [position, setPosition] = useState({ x: 0, y: 0 }) useEffect(() => { const move = (e) => {

  • 详解如何使用React Hooks请求数据并渲染

    前言 在日常的开发中,从服务器端异步获取数据并渲染是相当高频的操作.在以往使用React Class组件的时候,这种操作我们已经很熟悉了,即在Class组件的componentDidMount中通过ajax来获取数据并setState,触发组件更新. 随着Hook的到来,我们可以在一些场景中使用Hook的写法来替代Class的写法.但是Hook中没有setState.componentDidMount等函数,又如何做到从服务器端异步获取数据并渲染呢?本文将会介绍如何使用React的新特性Hook

  • react hooks实现原理解析

    目录 react hooks 实现 Hooks 解决了什么问题 Hooks API 类型 首先接触到的是 State hooks 其次接触到的是 Effect hooks 最后接触到的是 custom hooks Hooks 实现方式 问题一:useState dispatch 函数如何与其使用的 Function Component 进行绑定 react hooks 实现 Hooks 解决了什么问题 在 React 的设计哲学中,简单的来说可以用下面这条公式来表示: UI = f(data)

  • react  Suspense工作原理解析

    目录 Suspense 基本应用 Suspense 原理 基本流程 源码解读 - primary 组件 源码解读 - 异常捕获 源码解读 - 添加 promise 回调 源码解读-Suspense 首次渲染 primary 组件加载完成前的渲染 primary 组件加载完成时的渲染 利用 Suspense 自己实现数据加载 Suspense 基本应用 Suspense 目前在 react 中一般配合 lazy 使用,当有一些组件需要动态加载(例如各种插件)时可以利用 lazy 方法来完成.其中

  • React Hooks核心原理深入分析讲解

    目录 Hooks 闭包 开始动手实现 将useState应用到组件中 过期闭包 模块模式 实现useEffect 支持多个Hooks Custom Hooks 重新理解Hooks规则 React Hooks已经推出一段时间,大家应该比较熟悉,或者多多少少在项目中用过.写这篇文章简单分析一下Hooks的原理,并带大家实现一个简易版的Hooks. 这篇写的比较细,相关的知识点都会解释,给大家刷新一下记忆. Hooks Hooks是React 16.8推出的新功能.以这种更简单的方式进行逻辑复用.之前

  • React合成事件原理解析

    目录 事件介绍 什么是事件? 举个栗子 代码实现 React合成事件基础知识 什么是合成事件? 在React中事件的写法和原生事件写法的区别? 为什么会有合成事件? 合成事件机制简述 React合成事件实现原理 事件注册 事件触发-事件监听器做了什么 React中模拟冒泡和捕获 总结 事件介绍 什么是事件? 事件是在编程时系统内发生的动作或者发生的事情,而开发者可以某种方式对事件做出回应,而这里有几个先决条件 事件对象 给事件对象注册事件,当事件被触发后需要做什么 事件触发 举个栗子 在机场等待

  • React服务端渲染原理解析与实践

    关于服务端渲染也就是我们说的SSR大多数人都听过这个概念,很多同学或许在公司中已经做过服务端渲染的项目了,主流的单页面应用比如说Vue或者React开发的项目采用的一般都是客户端渲染的模式也就是我们说的CSR. 但是这种模式会带来明显的两个问题,第一个就是TTFP时间比较长,TTFP指的就是首屏展示时间,同时不具备SEO排名的条件,搜索引擎上排名不是很好.所以我们可以借助一些工具来进行改良我们的项目,将单页面应用编程服务器端渲染项目,这样就可以解决掉这些问题了. 目前主流的服务器端渲染框架也就是

  • react 实现图片正在加载中 加载完成 加载失败三个阶段的原理解析

    最近博客写道项目列表中,发现这里比较多图片,一开加载会比较慢,然后就想要用一个loading的图片来占位.与此同时,如果图片加载失败那么显示错误的图片,不显示一个原有的错误,那样比较难看. 效果 原理解析 这个就是一个组件,一个图片展示的组件,直接更改img标签的url地址就好,对的,是这样的,在vue中直接更改地址,vue会有响应式的更新数据. 图片的事件 图片是有许多的事件的,例如,onload, onerror等,图片只要一加载就会调用onload的事件,不管是加载成功还是加载失败都会调用

  • react diff 算法实现思路及原理解析

    目录 事例分析 diff 特点 diff 思路 实现 diff 算法 修改入口文件 实现 React.Fragment 我们需要修改 children 对比 前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有我们常见的 key 的作用. diff 算法使用在子都是数组的情况下,这点和 vue 是一样的.如果元素是其他类型的话直接替换就好. 事例分析 按照之前的 diff 写法,如果元素不

  • react fiber执行原理示例解析

    目录 为什么要使用fiber,要解决什么问题? fiber是什么? 数据结构 执行单元 浏览器工作: Fiber执行原理 workInProgress tree: currentFiber tree: Effects list: render阶段: 遍历节点过程: 收集effect list: commit阶段: 为什么commit必须是同步的操作的? 为什么要使用fiber,要解决什么问题? 在 react16 引入 Fiber 架构之前,react 会采用递归方法对比两颗虚拟DOM树,找出需

  • React setState是异步还是同步原理解析

    目录 setState异步更新 那么为什么setState设计为异步呢? 如何获取异步的结果 setState一定是异步的吗? setState异步更新 开发中当组件中的状态发生了变化,页面并不会重新渲染.我们必须要通过setState来告知React数据已经发生了变化,重新渲染页面. 先来看下面的例子: constructor() { super(); this.state = { message: "Hello World", }; } changeText() { this.se

  • React不使用requestIdleCallback实现调度原理解析

    目录 1.起因 2.查找问题 3.解决问题 4.总结 5.吐槽 1.起因 最近在一边啃源码,一边手写fiber嘛,然后也看了很多博客和资料,基本上大伙好像都是说用requestIdleCallback来模拟react实现一个空闲时间调度.但我自己手写的时候把怎么用怎么怪,老是感觉有什么地方不对劲而且是在调度过程中,可能是因为我是想写出来来一个相对健全一点的模版方便我以后写源码的其他部分把,然后分析了一下所以有了这篇博客. 2.查找问题 1.requestIdleCallback是利用帧之间空闲时

随机推荐