react 零碎知识

react 零碎知识

如何遍历 react 中子节点

React.Children.map

React.Children.map(children, function[(thisArg)])
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。

React.Children.forEach

React.Children.forEach(children, function[(thisArg)])
与 React.Children.map() 类似,但它不会返回一个数组。

React.Children.count(children)

返回 children 中的组件总数量,等同于通过 map 或 forEach 调用回调函数的次数。

React.Children.only(children)

验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。

setState 是同步还是异步

setState

在开发中我们并不能直接通过修改 state 的值来让界面发生更新
因为修改了 state 之后,希望 React 根据最新的 State 来重新渲染界面,但是这种方式的修改 React 并不知道数据发生了变化
React 并没有实现类似于 Vue2 中的 Object.defineProperty 或者 Vue3 中的 Proxy 的方式来监听数据的变化
必须通过 setState 来告知 React 数据已经发生了变化

异步更新

React 在执行 setState 的时候会把更新的内容放入队列在事件执行结束后会计算 state 的数据,然后执行回调最后根据最新的 state 计算虚拟 DOM 更新真实 DOM

优点

保持内部一致性。如果改为同步更新的方式,尽管 setState 变成了同步,但是 props 不是
为后续的架构升级启用并发更新,React 会在 setState 时,根据它们的数据来源分配不同的优先级,这些数据来源有:事件回调句柄、动画效果等,再根据优先级并发处理,提升渲染性能
setState 设计为异步,可以显著的提升性能

同步执行

在 React 的生命周期函数和合成事件中可以修改批量更新的变量 isBatchingUpdates
可以设置为批量,其它地方如 addEventListener、setTimeout 和 setInterval 里无法设置

强行批量更新

import _ as React from 'react';
import _ as ReactDOM from 'react-dom';

class Counter extends React.Component{
state = {number:0}
buttonClick = ()=>{
console.log('buttonClick');// 2234
setTimeout(()=>{
     ReactDOM.unstable_batchedUpdates(()=>{
       this.setState((state)=>({number:state.number+1}));
       console.log(this.state.number);
     });
      });
  }
  divClick = ()=>{
  console.log('divClick');
  }
  render(){
  return (
  <div onClick={this.divClick} id="counter">
  <p>{this.state.number}</p>
  <button onClick={this.buttonClick}>+</button>
  </div>
  )
  }
  }
  ReactDOM.render(<Counter/>,document.getElementById('root'));

并发更新

启用 concurrent 模式

  • ReactDOM.unstable_createRoot(document.getElementById(‘root’)).render();
  • ReactDOM.createRoot(document.getElementById(‘root’)).render();
让 setState 表现为同步

setTimeout

上面我们讲到了,setState 本身并不是一个异步方法,其之所以会表现出一种异步的形式,是因为 react 框架本身的一个性能优化机制。那么基于这一点,如果我们能够越过 react 的机制,是不是就可以令 setState 以同步的形式体现了呢?
说再多文字不如代码实践,实践才是检验真理的唯一标准,下面我们还是以之前的例子为基础改造一下代码:

state = {
    number:1
};
componentDidMount(){
    setTimeout(()=>{
      this.setState({number:3})
      console.log(this.state.number)
    },0)
}

state = {
    number:1
};
componentDidMount() {
    document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
    this.setState({
      number: 3
    })
    console.log(this.state.number)
}
vue 和 react 的共同点

数据驱动视图
在 jquery 时代,我们需要频繁的操作 DOM 来实现页面效果与交互;而 Vue 和 React 解决了这一痛点,采用数据驱动视图方式,隐藏操作 DOM 的频繁操作。所以我们在开发时,只需要关注数据变化即可,但是二者实现方式不尽相同。

组件化
React 与 Vue 都遵循组件化思想,它们把注意力放在 UI 层,将页面分成一些细块,这些块就是组件,组件之间的组合嵌套就形成最后的网页界面。

所以在开发时都有相同的套路,比如都有父子组件传递, 都有数据状态管理、前端路由、插槽等。

Virtual DOM
Vue 与 React 都使用了 Virtual DOM + Diff 算法, 不管是 Vue 的 Template 模板+options api 写法, 还是 React 的 Class 或者 Function 写法,最后都是生成 render 函数,而 render 函数执行返回 VNode(虚拟 DOM 的数据结构,本质上是棵树)。

当每一次 UI 更新时,总会根据 render 重新生成最新的 VNode,然后跟以前缓存起来老的 VNode 进行比对,再使用 Diff 算法(框架核心)去真正更新真实 DOM(虚拟 DOM 是 JS 对象结构,同样在 JS 引擎中,而真实 DOM 在浏览器渲染引擎中,所以操作虚拟 DOM 比操作真实 DOM 开销要小的多)

vue 和 react 的不同点

核心思想不同

Vue 早期开发就尤雨溪大佬,所以定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。这就有了 vue 的主要特点:灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确。

React 从一开始的定位就是提出 UI 开发的新思路。背靠大公司 Facebook 的 React,从开始起就不缺关注和用户,而且 React 想要做的是用更好的方式去颠覆前端开发方式。所以 React 推崇函数式编程(纯组件),数据不可变以及单向数据流,当然需要双向的地方也可以手动实现, 比如借助 onChange 和 setState 来实现。

组件写法差异

React 推荐的做法是 JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中,即 all in js; Vue 推荐的做法是 template 的单文件组件格式(简单易懂,从传统前端转过来易于理解),即 html,css,JS 写在同一个文件(vue 也支持 JSX 写法)

react 首先对新集合进行遍历,for( name in nextChildren)。

通过唯一 key 来判断老集合中是否存在相同的节点。如果没有的话创建

如果有的话,if (preChild === nextChild )

会将节点在新集合中的位置和在老集合中 lastIndex 进行比较

如果 if (child._mountIndex < lastIndex) 进行移动操作,否则不进行移动操作。

如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作

Vue 的 Diff 算法核心实现

updateChildren 是 vue diff 的核心, 过程可以概括为:

旧 children 和新 children 各有两个头尾的变量 StartIdx 和 EndIdx,它们的 2 个变量相互比较,一共有 4 种比较方式。

如果 4 种比较都没匹配,如果设置了 key,就会用 key 进行比较,在比较的过程中,变量会往中间靠,一旦 StartIdx>EndIdx 表明旧 children 和新 children 至少有一个已经遍历完了,就会结束比较。

响应式原理不同

Vue

Vue 依赖收集,自动优化,数据可变。

Vue 递归监听 data 的所有属性,直接修改。

当数据改变时,自动找到引用组件重新渲染。

React

React 基于状态机,手动优化,数据不可变,需要 setState 驱动新的 state 替换老的 state。当数据改变时,以组件为根目录,默认全部重新渲染, 所以 React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制

其他不同点

除了上面的四个点外,细数还有很多不同点的, 比如 api 的差异也挺大的,Vue 为了更加简单易用,引入了指令、filter 等概念以及大量的 option API,比如 watch、computed 等都是非常好用的。

而 React 的 API 比较少, 如果你的 JavaScript 基础比较好,上手也是比较容易。

useLayoutEffect 与 useEffect 的区别

useEffect 是异步执行,而且是在渲染被绘制到屏幕之后执行。
流程如下:
你以某种方式触发了 rerender(改变 state,或者父组件发生 rerender)
React 渲染你的组件(调用组件函数)
屏幕在视觉上更新(真实 dom 操作)
然后 useEffect 运行

useLayoutEffect 是同步执行,时机在渲染之后但在屏幕更新之前。

流程如下:
你以某种方式触发了 rerender(改变 state,或者父组件发生 rerender)
React 渲染你的组件(调用组件函数)
useLayoutEffect 运行,React 等待它完成
屏幕在视觉上更新(真实 dom 操作)

react 在构建 fiber 的时候会调用 requestIdleCallback 在浏览器空余时间构建,也就是不会阻塞浏览器处理用户事件等,也可以称为异步执行。useEffect 就是异步非阻塞执行,而 useLayoutEffect 是同步阻塞执行

useState 与 useEffect 的实现原理
import {scheduleUpdateOnFiber} from "./scheduler"

let currentlyRenderingFiber = null

let workInProgressHook = null

let currentHook = null

let ReactCurrentDispatcher = {

  current: null

}



const HooksDispatcherOnMount = {

  useReducer: mountReducer,

  useState: mountState

}



const HooksDispatcherOnUpdate = {

  useReducer: updateReducer,

  useState: updateState

}



export function renderWithHooks(workInProgress){

  currentlyRenderingFiber = workInProgress

  workInProgress.memoizedState = null;

  if(!workInProgress.alternate){

​    ReactCurrentDispatcher.current = HooksDispatcherOnMount;

  }else{

​    ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;

  }

  workInProgressHook = null

  currentHook = null

}



function updateReducer(reducer){

  const hook = new updateWorkInProgressHook()

  const queue = hook.queue

  let pendingQueue = queue.pending

  const current = currentHook

  if(pendingQueue !== null){

​    let first = pendingQueue.next

​    let update = first

​    let newState = current.memoizedState

​    do{

​      newState = reducer(newState, update.action)

​      update = update.next

​    }while(update !== null && update !== first)

​    hook.memoizedState = newState

​    queue.pending = null

  }

  const dispatch = dispatchAction.bind(null,currentlyRenderingFiber,queue);

  return [hook.memoizedState, dispatch]

}

function updateState(initialState) {

  return updateReducer(basicStateReducer, initialState);

}

function basicStateReducer(state, action) {

  return typeof action === 'function' ? action(state) : action;

}

function mountState(initialState) {

  const hook = mountWorkInProgressHook();

  hook.memoizedState = initialState;

  const queue = (hook.queue = { pending: null,lastRenderedReducer: basicStateReducer, lastRenderedState: initialState });

  const dispatch = dispatchAction.bind(null, currentlyRenderingFiber, queue)

  return [hook.memoizedState, dispatch];

}

function mountReducer(reducer, initialState){

  const hook = mountWorkInProgressHook()

  hook.memoizedState = initialState

  const queue = (hook.queue = {

​     pending: null,

​     lastRenderedReducer: reducer,

​     lastRenderedState: initialState

​    });

  const dispatch = dispatchAction.bind(null,currentlyRenderingFiber,queue);

  return [hook.memoizedState, dispatch]

}

function dispatchAction(currentFiber, queue, action){

  const update = { action, next: null };

  const pending = queue.pending;

  if (pending === null) {

​    update.next = update;

  } else {

​    update.next = pending.next;

​    pending.next = update;

  }

  queue.pending = update;

  let lastRenderedReducer = queue.lastRenderedReducer

  let currentState = queue.lastRenderedState

  let eagerState = lastRenderedReducer(currentState, action);

  if(Object.is(eagerState, currentState)){

​    return

  }

  scheduleUpdateOnFiber(currentFiber)

}

function updateWorkInProgressHook(){

  let nextCurrentHook;

  // 找出对应的hook

  if(currentHook == null){

​    let current = currentlyRenderingFiber.alternate

​    nextCurrentHook = current.memoizedState

  }else {

​    nextCurrentHook = currentHook.next

  }

  currentHook = nextCurrentHook

  let newHook = {

​    memoizedState: nextCurrentHook.memoizedState,

​    queue: nextCurrentHook.queue,

​    next: null

  }

  // 构建新的fiber hook 链表

  if(workInProgressHook === null){

​    currentlyRenderingFiber.memoizedState = workInProgressHook = newHook

  }else {

​    workInProgressHook = workInProgressHook.next = newHook

  }

  return workInProgressHook

}

function mountWorkInProgressHook(){

  let hook = {

​    memoizedState: null,

​    queue: null,

​    next: null

  }

  if(workInProgressHook === null){

​    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;

  }else {

   // hook.next = workInProgressHook

​    workInProgressHook = workInProgressHook.next = hook

  }

  return workInProgressHook

}

export function useReducer(reducer, initialState){

  return ReactCurrentDispatcher.current.useReducer(reducer, initialState)

}

export function useState(initialState){

  return ReactCurrentDispatcher.current.useState(initialState)

}
react 的渲染流程

react 内部渲染分为三个阶段 分别是 scheduler 调度、 recoilcler 调和、commit 提交三个阶段

调度就是首次渲染或者更新的时候根据 requestIdleCallback 分配时间片,调和阶段就是根据 jsx 生成 Fiber 节点并进行 Dom-diff 的节点生成 Fiber 树和 effectList 副作用链的阶段 ,commit 就是根据 effectList 将 react Fiber 挂载到 Root DOM 的阶段,前两个阶段都打断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶落风尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值