React Hooks 基础、实现、原理

news/2024/7/15 19:17:59 标签: react.js, 前端, javascript

React Hooks 基础、实现、原理

  • 题外话
    • 为什么要有Hooks?
        • 但是Class Component 的用法也有缺陷:
          • 1.组件复用变的困难
          • 2.JavaScript本身的缺陷
        • 函数式
    • React Hooks
        • useState
        • useEffect
        • useCallback、useMemo
        • useReducer
    • 最后

题外话

2023了,新年快乐!转眼间就已经工作一年左右了,这一年提升很多,想到很久没有整理知识点了所以…
在这里插入图片描述

为什么要有Hooks?

官方给出的解释是:复杂组件变得难以理解、组件之间复用状态逻辑很难

React是以组件为粒度编排应用的,组件是代码复用的最小单元。
在设计上,React采用props属性来接收外部的数据,使用state属性来管理组件自身产生的数据(状态),也就是说props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。而为了实现(运行时)对数据变更做出响应需要,React采用基于类(Class)的组件设计。除此之外,React认为组件是有生命周期的。

但是Class Component 的用法也有缺陷:

1.组件复用变的困难

HOC是React中用于重用组件逻辑的一种高级技术实现模式,它本身是一个函数,接受一个组件并返回一个新的组件。 而HOC容易产生嵌套地狱,每一次HOC调用都会产生一个组件实例。

2.JavaScript本身的缺陷

稍微不慎就会出现因this的指向报错。没有类似Java/C++多继承的概念,类的逻辑复用是个问题。

函数式

当然React不只是只有类式写法,还有函数式,但是早期的函数式组件只是单纯地接收props、绑定事件、返回jsx,本身是无状态的组件,依赖props传入的handle来响应数据(状态)的变更,所以函数式组件不能脱离Class Comnent来存在。
这时候就诞生了Hook,使得组件自身能够通过某种机制再触发状态的变更并且引起re-render

React Hooks

useState

useState可以管理组件自身定义的状态,返回一个 state,以及更新 state 的函数。
如下:setCount函数用于更新 count,它接收一个新的 state 值并将组件的一次重新渲染加入队列,并且引起组件re-render。在组件在初始渲染期间,返回的状态 (state) 与传入的第一个参数值相同也就是0。
并且count只能通过setCount来改变。

  import React, { useState } from 'react';
  	function Add() {
 	const [count, setCount] = useState(0);
	return (
		<div>
		<p>You clicked {count} times</p>
		<button onClick={() => setCount(count + 1)}>
		Click me
		</button>
		</div>
	 );
	}

dispatchAction函数是更新state的关键,它会生成一个update挂载到Hooks队列上面,并提交一个React更新调度,后续的工作和类组件一致。
理论上可以同时调用多次dispatch,但只有最后一次会生效(queue的last指针指向最后一次update的state)
useState更新数据是直接覆盖的。

// useState() 首次render时执行mountState
function mountState(initialState) {
  // 从当前Fiber生成一个新的hook对象,将此hook挂载到Fiber的hook链尾,并返回这个hook
  var hook = mountWorkInProgressHook();

  hook.memoizedState = hook.baseState = initialState;

  var queue = hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: (state, action) => isFn(state) ? action(state) : action,
    lastRenderedState: initialState
  };
  // currentlyRenderingFiber$1保存当前正在渲染的Fiber节点
  // 将返回的dispatch和调用hook的节点建立起了连接,同时在dispatch里边可以访问queue对象
  var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
  return [hook.memoizedState, dispatch];
}

 功能相当于setState!
function dispatchAction(fiber, queue, action) {
  ...
  var update = {
    action, // 接受普通值,也可以是函数
    next: null,
  };
  var last = queue.last;

  if (last === null) {
    update.next = update;
  } else {
    last.next = update;
  }

  // 略去计算update的state过程
  queue.last = update;
  ...
  // 触发React的更新调度,scheduleWork是schedule阶段的起点
  scheduleWork(fiber, expirationTime);
}

useEffect

useEffect 可以在组件渲染后实现各种不同的副作用。有些副作用可能需要清除,所以需要返回一个函数,有些不需要。

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

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。。
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行,也就是说可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组,当它改变时effect 才会执行(每次改变后渲染结束),如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

 useEffect(() => {
    document.title = `You clicked ${count} times`;
  },[]);

与useState传入的是具体state不同,useEffect传入的是一个callback函数,与useState最大的不同是执行时机,useEffect callback是在组件被渲染为真实DOM后执行(所以可以用于DOM操作)
useEffect调用也会在当前Fiber节点的Hooks链追加一个hook并返回,它的memoizedState存放一个effect对象,effect对象最终会被挂载到Fiber节点的updateQueue队列(当Fiber节点都渲染到页面上后,就会开始执行Fiber节点中的updateQueue中所保存的函数)

HooksDispatcherOnMountInDEV = {
    useEffect: function() {
    currentHookNameInDev = 'useEffect';
    ...
    return mountEffectImpl(Update | Passive, UnmountPassive | MountPassive, create, deps);
  },
};

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  return hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
}

function pushEffect(tag, create, destroy, deps) {
  var effect = {
    tag: tag,
    create: create, // 存储useEffect传入的callback
    destroy: destroy, // 存储useEffect传入的callback的返回函数,用于effect清理
    deps: deps,
    next: null
  };
  .....
  componentUpdateQueue = createFunctionComponentUpdateQueue();
  componentUpdateQueue.lastEffect = effect.next = effect;
  ....
  return effect;
}

function renderWithHooks() {
    ....
  currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
  ....
}

useCallback、useMemo

useCallback本质上是添加了一层依赖检查。它以另一种方式解决了问题 - 我们使函数本身只在需要的时候才改变,而不是去掉对函数的依赖。
如果count保持不变,getClickNum 也会保持不变,我们的effect也不会重新运行。但是如果count修改了,getClickNum 也会随之改变,因此会重新请求数据。
返回一个 memoized 回调函数。

 const getClickNum = useCallback(() => {
    return `You clicked ${count} times`;
  }, [count]);

useMemo用于缓存一些耗时的计算结果,只有当依赖参数改变时才重新执行计算
返回一个 memoized 值。

 const getNum = useMemo(() => getcount(count)
   , [count]);

简单理解:useCallback(fn, deps) === useMemo(() => fn, deps)

useReducer

useReducer用于管理复杂的数据结构,基本实现了redux的核心功能。当你想更新一个状态,并且这个状态更新依赖于另一个状态的值时,就可以用useReducer。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

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:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

最后

1.Hooks的调用尽量只在顶层作用域进行调用。
2.不要在循环、条件或者是嵌套函数中调用Hook,否则可能会无法确保每次组件渲染时都以相同的顺序调用Hook。
3.Hooks 的串联不是一个数组,是一个链式的数据结构,从根节点 workInProgressHook 向下通过 next 进行串联。
4.React Hooks目前只支持函数组件


http://www.niftyadmin.cn/n/25235.html

相关文章

MySQL常用基础 - 小白必看

MySQL数据库基本操作 一、DDL 概念&#xff1a;是一个数据定义语言 该语言部分包括&#xff1a; 1、对数据库的常用操作 创建数据库&#xff1a; 1、create database 数据库名 (直接删除) 2、create database if not exists 数据库名 &#xff08;判断数据库是否存在&…

【博客576】警惕docker本身iptables规则对网络的影响

警惕docker本身iptables规则对网络的影响 警惕1&#xff1a;k8s环境下&#xff0c;独立拉取docker容器时&#xff0c;进行端口映射会有问题 场景&#xff1a; 在k8s节点由于某种原因&#xff0c;比如&#xff1a;需要拉起一个docker环境来制作镜像&#xff0c;需要拉起一些不…

Redis常见集群方案

Redis常见集群方案 Redis集群方案目前主流的有三种&#xff0c;分别是Twemproxy、Codis和Redis Cluster。 Redis Cluster Redis Cluster 集群是去中心化通过客户端分片的结构&#xff0c;集群元数据信息分布在每个节点上&#xff0c;主备切换依赖于多个节点协商选主。 Red…

你是真的“C”——详解C语言实现静态版通讯录

详解C语言实现静态版通讯录&#x1f60e;前言&#x1f603;通讯录设计的关键思想点分析 &#x1f64c;通讯录界面&#xff08;meun&#xff09;设计 &#x1f64c;增加信息功能实现代码&#x1f64c;删除信息功能实现代码&#x1f64c;查询信息功能实现代码&#x1f64c;修改信…

24-Golang中字符串中常用的系统函数!!!

字符串中常用的系统函数 字符串在程序开发中&#xff0c;使用的是非常多的&#xff0c;常用的函数如下&#xff1a; 1.统计字符串的长度&#xff0c;按字节len(str)2.字符串遍历&#xff0c;同时处理有有中文的问题 r : []rune(str)3.字符串转整数&#xff1a; n, err : strc…

GPS北斗卫星同步时钟(NTP时间服务器)助力某局指挥中心

GPS北斗卫星同步时钟&#xff08;NTP时间服务器&#xff09;助力某局指挥中心 GPS北斗卫星同步时钟&#xff08;NTP时间服务器&#xff09;助力某局指挥中心 以大数据为核心的数据服务层&#xff0c;围绕各警种业务需求&#xff0c;实现时空关联、综合研判、立体防控等各类实战…

【自用】高频电子线路复习(更新中)

疫情原因 没有考试就放假回家了 返校后将先进行死亡考试周 七天考完九门 回校再进行极限复习只能说可以通过 而不利于绩点的提升 所以要从现在开始抽取一些时间进行学习 第七章 频率变换方法与电路分析 7.1 非线性电路包括 发送端的高频振荡器、倍频器、谐振功率放大器和调…

【django】关联模型类中数据的增删改查操作总结

文章目录一、多对一正向操作1、改方法一方法二2、删3、查反向操作案例1&#xff1a;查询百度渠道下的所有学生信息案例2&#xff1a;新增一个百度渠道下的学生1、增直接创建Student对象2、改方法一&#xff1a;add()案例1&#xff1a;将s1,s2,s3添加到百度渠道中方法二:替换对象…