乐趣区

关于react.js:社招前端二面react面试题整理

解释 React 中 render() 的目标。

每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的示意。如果须要渲染多个 HTML 元素,则必须将它们组合在一个关闭标记内,例如 <form><group><div> 等。此函数必须放弃污浊,即必须每次调用时都返回雷同的后果。

createElement 过程

React.createElement():依据指定的第一个参数创立一个 React 元素

React.createElement(
  type,
  [props],
  [...children]
)
  • 第一个参数是必填,传入的是似 HTML 标签名称,eg: ul, li
  • 第二个参数是选填,示意的是属性,eg: className
  • 第三个参数是选填, 子节点,eg: 要显示的文本内容
// 写法一:var child1 = React.createElement('li', null, 'one');
    var child2 = React.createElement('li', null, 'two');
    var content = React.createElement('ul', { className: 'teststyle'}, child1, child2); // 第三个参数能够离开也能够写成一个数组
      ReactDOM.render(
          content,
        document.getElementById('example')
      );

// 写法二:var child1 = React.createElement('li', null, 'one');
    var child2 = React.createElement('li', null, 'two');
    var content = React.createElement('ul', { className: 'teststyle'}, [child1, child2]);
      ReactDOM.render(
          content,
        document.getElementById('example')
      );

组件之间传值

  • 父组件给子组件传值

    在父组件中用标签属性的 = 模式传值

    在子组件中应用 props 来获取值

  • 子组件给父组件传值

    在组件中传递一个函数

    在子组件中用 props 来获取传递的函数,而后执行该函数

    在执行函数的时候把须要传递的值当成函数的实参进行传递

  • 兄弟组件之间传值

    利用父组件

    先把数据通过【子组件】===》【父组件】

    而后在数据通过【父组件】===〉【子组件】

    音讯订阅

    应用 PubSubJs 插件

应用 React Hooks 益处是啥?

首先,Hooks 通常反对提取和重用跨多个组件通用的有状态逻辑,而无需承当高阶组件或渲染 props 的累赘。Hooks 能够轻松地操作函数组件的状态,而不须要将它们转换为类组件。
Hooks 在类中不起作用,通过应用它们,咱们能够完全避免应用生命周期办法,例如 componentDidMountcomponentDidUpdatecomponentWillUnmount。相同,应用像 useEffect 这样的内置钩子。

React 中的 useState() 是什么?

上面阐明 useState(0) 的用处:

const [count, setCounter] = useState(0);
const [moreStuff, setMoreStuff] = useState();

const setCount = () => {setCounter(count + 1);
  setMoreStuff();};

useState 是一个内置的 React Hook。useState(0) 返回一个元组,其中第一个参数 count 是计数器的以后状态,setCounter 提供更新计数器状态的办法。
咱们能够在任何中央应用 setCounter 办法更新计数状态 - 在这种状况下,咱们在 setCount 函数外部应用它能够做更多的事件,应用 Hooks,可能使咱们的代码放弃更多功能,还能够防止过多应用基于类的组件。

什么是 React Hooks?

Hooks是 React 16.8 中的新增加内容。它们容许在不编写类的状况下应用 state 和其余 React 个性。应用 Hooks,能够从组件中提取有状态逻辑,这样就能够独立地测试和重用它。Hooks 容许咱们在不扭转组件层次结构的状况下重用有状态逻辑,这样在许多组件之间或与社区共享 Hooks 变得很容易。

参考 前端进阶面试题具体解答

和谐阶段 setState 外部干了什么

  • 当调用 setState 时,React 会做的第一件事件是将传递给 setState 的对象合并到组件的以后状态
  • 这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最无效的形式,依据这个新的状态来更新 UI。为此,React 将构建一个新的 React 元素树(您能够将其视为 UI 的对象示意)
  • 一旦有了这个树,为了弄清 UI 如何响应新的状态而扭转,React 会将这个新树与上一个元素树相比拟(diff)

通过这样做,React 将会晓得产生的确切变动,并且通过理解产生什么变动,只需在相对必要的状况下进行更新即可最小化 UI 的占用空间

为什么 JSX 中的组件名要以大写字母结尾

因为 React 要晓得以后渲染的是组件还是 HTML 元素

为什么不间接更新 state 呢 ?

如果试图间接更新 state,则不会从新渲染组件。

// 谬误
This.state.message = 'Hello world';

须要应用 setState() 办法来更新 state。它调度对组件 state 对象的更新。当 state 扭转时,组件通过从新渲染来响应:

// 正确做法
This.setState({message:‘Hello World’});

React 组件生命周期有哪些不同阶段?

在组件生命周期中有四个不同的阶段:

  1. Initialization:在这个阶段,组件筹备设置初始化状态和默认属性。
  2. Mounting:react 组件曾经筹备好挂载到浏览器 DOM 中。这个阶段包含 componentWillMountcomponentDidMount生命周期办法。
  3. Updating:在这个阶段,组件以两种形式更新,发送新的 props 和 state 状态。此阶段包含 shouldComponentUpdatecomponentWillUpdatecomponentDidUpdate生命周期办法。
  4. Unmounting:在这个阶段,组件曾经不再被须要了,它从浏览器 DOM 中卸载下来。这个阶段蕴含 componentWillUnmount 生命周期办法。
    除以上四个罕用生命周期外,还有一个错误处理的阶段:
    Error Handling:在这个阶段,不管在渲染的过程中,还是在生命周期办法中或是在任何子组件的构造函数中产生谬误,该组件都会被调用。这个阶段蕴含了 componentDidCatch 生命周期办法。

在 React 中元素(element)和组件(component)有什么区别?

简略地说,在 React 中元素(虛拟 DOM)形容了你在屏幕上看到的 DOM 元素。
换个说法就是,在 React 中元素是页面中 DOM 元素的对象示意形式。在 React 中组件是一个函数或一个类,它能够承受输出并返回一个元素。
留神:工作中,为了进步开发效率,通常应用 JSX 语法示意 React 元素(虚构 DOM)。在编译的时候,把它转化成一个 React. createElement 调用办法。

为什么类办法须要绑定到类实例?

在 JS 中,this 值会依据以后上下文变动。在 React 类组件办法中,开发人员通常心愿 this 援用组件的以后实例,因而有必要将这些办法绑定到实例。通常这是在构造函数中实现的:

class SubmitButton extends React.Component {constructor(props) {super(props);
    this.state = {isFormSubmitted: false,};
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleSubmit() {
    this.setState({isFormSubmitted: true,});
  }
  render() {return <button onClick={this.handleSubmit}>Submit</button>;
  }
}

React 的生命周期办法有哪些?

  • componentWillMount: 在渲染之前执行,用于根组件中的 App 级配置。
  • componentDidMount:在第一次渲染之后执行,能够在这里做 AJAX 申请,DOM 的操作或状态更新以及设置事件监听器。
  • componentWillReceiveProps:在初始化 render 的时候不会执行,它会在组件承受到新的状态 (Props) 时被触发,个别用于父组件状态更新时子组件的从新渲染
  • shouldComponentUpdate:确定是否更新组件。默认状况下,它返回true。如果确定在 stateprops 更新后组件不须要在从新渲染,则能够返回false,这是一个进步性能的办法。
  • componentWillUpdate:在 shouldComponentUpdate 返回 true 确定要更新组件之前件之前执行。
  • componentDidUpdate:它次要用于更新 DOM 以响应 propsstate更改。
  • componentWillUnmount:它用于勾销任何的网络申请,或删除与组件关联的所有事件监听器。

什么是高阶组件?

高阶组件 (HOC) 是承受一个组件并返回一个新组件的函数。基本上,这是一个模式,是从 React 的组合个性中衍生进去的,称其为 纯组件,因为它们能够承受任何动静提供的子组件,但不会批改或复制输出组件中的任何行为。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOC 能够用于以下许多用例

  • 代码重用、逻辑和疏导形象
  • 渲染劫持
  • state 形象和操作
  • props 解决

redux 有什么毛病

  • 一个组件所须要的数据,必须由父组件传过来,而不能像 flux 中间接从 store 取。
  • 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新 render,可能会有效率影响,或者须要写简单的shouldComponentUpdate 进行判断。

什么是 React 的 refs?为什么它们很重要

refs 容许你间接拜访 DOM 元素或组件实例。为了应用它们,能够向组件增加个 ref 属性。
如果该属性的值是一个回调函数,它将承受底层的 DOM 元素或组件的已挂载实例作为其第一个参数。能够在组件中存储它。

export class App extends Component {showResult() {console.log(this.input.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref={(input) => (this.input = input)} />
        <button onClick={this.showResult.bind(this)}> 展现后果 </button>
      </div>
    );
  }
}

如果该属性值是一个字符串,React 将会在组件实例化对象的 refs 属性中,存储一个同名属性,该属性是对这个 DOM 元素的援用。能够通过原生的 DOM API 操作它。

export class App extends Component {showResult() {console.log(this.refs.username.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref="username" />
        <button onClick={this.showResu1t.bind(this)}> 展现后果 </button>
      </div>
    );
  }
}

React 中的状态是什么?它是如何应用的

状态是 React 组件的外围,是数据的起源,必须尽可能简略。基本上状态是确定组件出现和行为的对象。与 props 不同,它们是可变的,并创立动静和交互式组件。能够通过 this.state() 拜访它们。

**

React 与 Vue 的 diff 算法有何不同?

diff 算法是指生成更新补丁的形式,次要利用于虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁。

React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对,别离是树、组件及元素,以此晋升效率。

  • 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
  • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
  • 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。

以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构。fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲实现。workInProgress 更新实现后,再通过批改 current 相干指针指向新节点。

Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。

对 React 实现原理的了解

简版

  • reactvue 都是基于 vdom 的前端框架,之所以用 vdom 是因为能够精准的比照关怀的属性,而且还能够跨平台渲染
  • 然而开发不会间接写 vdom,而是通过 jsx 这种靠近 html 语法的 DSL,编译产生 render function,执行后产生 vdom
  • vdom 的渲染就是依据不同的类型来用不同的 dom api 来操作 dom
  • 渲染组件的时候,如果是函数组件,就执行它拿到 vdomclass 组件就创立实例而后调用 render 办法拿到 vdomvue 的那种 option 对象的话,就调用 render 办法拿到 vdom
  • 组件实质上就是对一段 vdom 产生逻辑的封装,函数classoption 对象甚至其余模式都能够
  • reactvue 最大的区别在状态治理形式上,vue 是通过响应式,react 是通过 setStateapi。我感觉这个是最大的区别,因为它导致了前面 react 架构的变更
  • reactsetState 的形式,导致它并不知道哪些组件变了,须要渲染整个 vdom 才行。然而这样计算量又会比拟大,会阻塞渲染,导致动画卡顿。所以 react 起初革新成了 fiber 架构,指标是可打断的计算
  • 为了这个指标,不能变比照变更新 dom 了,所以把渲染分为了 rendercommit 两个阶段,render 阶段通过 schedule 调度来进行 reconcile,也就是找到变动的局部,创立 dom,打上增删改的 tag,等全副计算完之后,commit 阶段一次性更新到 dom
  • 打断之后要找到父节点、兄弟节点,所以 vdom 也被革新成了 fiber 的数据结构,有了 parentsibling 的信息
  • 所以 fiber 既指这种链表的数据结构,又指这个 rendercommit 的流程
  • reconcile 阶段每次解决一个 fiber 节点,解决前会判断下 shouldYield,如果有更高优先级的工作,那就先执行别的
  • commit 阶段不必再次遍历 fiber 树,为了优化,react 把有 effectTagfiber 都放到了 effectList 队列中,遍历更新即可
  • dom 操作前,会异步调用 useEffect 的回调函数,异步是因为不能阻塞渲染
  • dom 操作之后,会同步调用 useLayoutEffect 的回调函数,并且更新 ref
  • 所以,commit 阶段又分成了 before mutationmutationlayout 这三个小阶段,就对应下面说的那三局部

了解了 vdomjsx组件实质 fiberrender(reconcile + schedule) + commit(before mutation、mutation、layout) 的渲染流程,就算是对 react 原理有一个比拟深的了解

上面开展剖析

vdom

为什么 reactvue 都要基于 vdom 呢?间接操作实在 dom 不行么?

思考下这样的场景:

  • 渲染就是用 dom api 对实在 dom 做增删改,如果曾经渲染了一个 dom,起初要更新,那就要遍历它所有的属性,从新设置,比方 idclasNameonclick 等。
  • dom 的属性是很多的:
  • 有很多属性基本用不到,但在更新时却要跟着从新设置一遍。
  • 能不能只比照咱们关怀的属性呢?
  • 把这些独自摘出来用 JS 对象示意不就行了?
  • 这就是为什么要有 vdom,是它的第一个益处。
  • 而且有了 vdom 之后,就没有和 dom 强绑定了,能够渲染到别的平台,比方 nativecanvas 等等。
  • 这是 vdom 的第二个益处。
  • 咱们晓得了 vdom 就是用 JS 对象示意最终渲染的 dom 的,比方:
{
  type: 'div',
  props: {
    id: 'aaa',
    className: ['bbb', 'ccc'],
    onClick: function() {}
  },
  children: []}

而后用渲染器把它渲染进去,然而要让开发去写这样的 vdom 么?那必定不行,这样太麻烦了,大家相熟的是 html 那种形式,所以咱们要引入编译的伎俩

dsl 的编译

  • dsldomain specific language,畛域特定语言的意思,htmlcss 都是 web 畛域的 dsl
  • 间接写 vdom 太麻烦了,所以前端框架都会设计一套 dsl,而后编译成 render function,执行后产生 vdom
  • vuereact 都是这样

这套 dsl 怎么设计呢?前端畛域大家相熟的形容 dom 的形式是 html,最好的形式天然是也设计成那样。所以 vuetemplatereactjsx 就都是这么设计的。vuetemplate compiler 是本人实现的,而 reactjsx 的编译器是 babel 实现的,是两个团队单干的后果。

编译成 render function 后再执行就是咱们须要的 vdom。接下来渲染器把它渲染进去就行了。那渲染器怎么渲染 vdom 的呢?

渲染 vdom

渲染 vdom 也就是通过 dom api 增删改 dom。比方一个 div,那就要 document.createElement 创立元素,而后 setAttribute 设置属性,addEventListener 设置事件监听器。如果是文本,那就要 document.createTextNode 来创立。所以说依据 vdom 类型的不同,写个 if else,别离做不同的解决就行了。没错,不论 vue 还是 react,渲染器里这段 if else 是少不了的:

switch (vdom.tag) {
  case HostComponent:
    // 创立或更新 dom
  case HostText:
    // 创立或更新 dom
  case FunctionComponent: 
    // 创立或更新 dom
  case ClassComponent: 
    // 创立或更新 dom
}

react 里是通过 tag 来辨别 vdom 类型的,比方 HostComponent 就是元素,HostText 就是文本,FunctionComponentClassComponent 就别离是函数组件和类组件。那么问题来了,组件怎么渲染呢?这就波及到组件的原理了:

组件

咱们的指标是通过 vdom 形容界面,在 react 里会应用 jsx。这样的 jsx 有的时候是基于 state 来动静生成的。如何把 statejsx 关联起来呢?封装成 functionclass 或者 option对象的模式。而后在渲染的时候执行它们拿到 vdom就行了。

这就是组件的实现原理:

switch (vdom.tag) {
  case FunctionComponent: 
       const childVdom = vdom.type(props);

       render(childVdom);
       //...
  case ClassComponent: 
     const instance = new vdom.type(props);
     const childVdom = instance.render();

     render(childVdom);
     //...
} 

如果是函数组件,那就传入 props 执行它,拿到 vdom 之后再递归渲染。如果是 class 组件,那就创立它的实例对象,调用 render 办法拿到 vdom,而后递归渲染。所以,大家猜到 vueoption 对象的组件形容形式怎么渲染了么?

{data: {},
    props: {}
    render(h) {return h('div', {}, '');
    }
}

没错,就是执行下 render 办法就行:

const childVdom = option.render();

render(childVdom);

大家可能平时会写单文件组件 sfc的模式,那个会有专门的编译器,把 template 编译成 render function,而后挂到 option 对象的render` 办法上

所以组件实质上只是对产生 vdom 的逻辑的封装,函数的模式、option 对象的模式、class 的模式都能够。就像 vue3 也有了函数组件一样,组件的模式并不重要。基于 vdom 的前端框架渲染流程都差不多,vue 和 react 很多方面是一样的。然而治理状态的形式不一样,vue 有响应式,而 react 则是 setStateapi 的形式。真说起来,vue 和 react 最大的区别就是状态治理形式的区别,因为这个区别导致了前面架构演变方向的不同。

状态治理

react 是通过 setStateapi 触发状态更新的,更新当前就从新渲染整个 vdom。而 vue 是通过对状态做代理,get 的时候收集以来,而后批改状态的时候就能够触发对应组件的 render 了。

有的同学可能会问,为什么 react 不间接渲染对应组件呢?

设想一下这个场景:

父组件把它的 setState 函数传递给子组件,子组件调用了它。这时候更新是子组件触发的,然而要渲染的就只有那个组件么?显著不是,还有它的父组件。同理,某个组件更新实际上可能触发任意地位的其余组件更新的。所以必须从新渲染整个 vdom 才行。

vue 为啥能够做到精准的更新变动的组件呢?因为响应式的代理呀,不论是子组件、父组件、还是其余地位的组件,只有用到了对应的状态,那就会被作为依赖收集起来,状态变动的时候就能够触发它们的 render,不论是组件是在哪里的。这就是为什么 react 须要从新渲染整个 vdom,而 vue 不必。这个问题也导致了起初两者架构上逐步有了差别。

react 架构的演变

  • react15 的时候,和 vue 的渲染流程还是很像的,都是递归渲染 vdom,增删改 dom 就行。然而因为状态治理形式的差别逐步导致了架构的差别。
  • reactsetState 会渲染整个 vdom,而一个利用的所有 vdom 可能是很宏大的,计算量就可能很大。浏览器里 js 计算工夫太长是会阻塞渲染的,会占用每一帧的动画、重绘重排的工夫,这样动画就会卡顿。作为一个有谋求的前端框架,动画卡顿必定是不行的。然而因为 setState 的形式只能渲染整个 vdom,所以计算量大是不可避免的。那能不能把计算量拆分一下,每一帧计算一部分,不要阻塞动画的渲染呢?顺着这个思路,react 就革新为了 fiber 架构。

fiber 架构

优化的指标是打断计算,分屡次进行,但当初递归的渲染是不能打断的,有两个方面的起因导致的:

  • 渲染的时候间接就操作了 dom 了,这时候打断了,那曾经更新到 dom 的那局部怎么办?
  • 当初是间接渲染的 vdom,而 vdom 里只有 children 的信息,如果打断了,怎么找到它的父节点呢?

第一个问题的解决还是容易想到的:

  • 渲染的时候不要间接更新到 dom 了,只找到变动的局部,打个增删改的标记,创立好 dom,等全副计算完了一次性更新到 dom 就好了。
  • 所以 react 把渲染流程分为了两局部:rendercommit
  • render 阶段会找到 vdom 中变动的局部,创立 dom,打上增删改的标记,这个叫做 reconcile,和谐。
  • reconcile 是能够打断的,由 schedule 调度。
  • 之后全副计算完了,就一次性更新到 dom,叫做 commit
  • 这样,react 就把之前的和 vue 很像的递归渲染,革新成了 render(reconcile + schdule)+ commit 两个阶段的渲染。
  • 从此以后,reactvue 架构上的差别才大了起来。

第二个问题,如何打断当前还能找到父节点、其余兄弟节点呢?

现有的 vdom 是不行的,须要再记录下 parentsilbing 的信息。所以 react 发明了 fiber 的数据结构。

  • 除了 children 信息外,额定多了 siblingreturn,别离记录着兄弟节点、父节点的信息。
  • 这个数据结构也叫做 fiber。(fiber 既是一种数据结构,也代表 render + commit 的渲染流程)react 会先把 vdom 转换成 fiber,再去进行 reconcile,这样就是可打断的了。
  • 为什么这样就能够打断了呢?因为当初不再是递归,而是循环了:
function workLoop() {while (wip) {performUnitOfWork();
  }

  if (!wip && wipRoot) {commitRoot();
  }
}
  • react 里有一个 workLoop 循环,每次循环做一个 fiberreconcile,以后解决的 fiber 会放在 workInProgress 这个全局变量上。
  • 当循环完了,也就是 wip 为空了,那就执行 commit 阶段,把 reconcile 的后果更新到 dom
  • 每个 fiberreconcile 是依据类型来做的不同解决。当解决完了以后 fiber 节点,就把 wip 指向 siblingreturn 来切到下个 fiber 节点。:
function performUnitOfWork() {const { tag} = wip;

  switch (tag) {
    case HostComponent:
      updateHostComponent(wip);
      break;

    case FunctionComponent:
      updateFunctionComponent(wip);
      break;

    case ClassComponent:
      updateClassComponent(wip);
      break;
    case Fragment:
      updateFragmentComponent(wip);
      break;
    case HostText:
      updateHostTextComponent(wip);
      break;
    default:
      break;
  }

  if (wip.child) {
    wip = wip.child;
    return;
  }

  let next = wip;

  while (next) {if (next.sibling) {
      wip = next.sibling;
      return;
    }
    next = next.return;
  }

  wip = null;
}

函数组件和 class 组件的 reconcile和之前讲的一样,就是调用 render 拿到 vdom,而后持续解决渲染出的 vdom

function updateClassComponent(wip) {const { type, props} = wip;
  const instance = new type(props);
  const children = instance.render();

  reconcileChildren(wip, children);
}

function updateFunctionComponent(wip) {renderWithHooks(wip);

  const {type, props} = wip;

  const children = type(props);
  reconcileChildren(wip, children);
}
  • 循环执行 reconcile,那每次解决之前判断一下是不是有更高优先级的工作,就能实现打断了。
  • 所以咱们在每次解决 fiber 节点的 reconcile 之前,都先调用下 shouldYield 办法:
function workLoop() {while (wip && shouldYield()) {performUnitOfWork();
  }

  if (!wip && wipRoot) {commitRoot();
  }
}
  • shouldYiled 办法就是判断待处理的工作队列有没有优先级更高的工作,有的话就先解决那边的 fiber,这边的先暂停一下。
  • 这就是 fiber 架构的 reconcile 能够打断的原理。通过 fiber 的数据结构,加上循环解决前每次判断下是否打断来实现的。
  • 聊完了 render 阶段(reconcile + schedule),接下来就进入 commit 阶段了。
  • 后面说过,为了变为可打断的,reconcile 阶段并不会真正操作 dom,只会创立 dom 而后打个 effectTag 的增删改标记。
  • commit 阶段就依据标记来更新 dom 就能够了。
  • 然而 commit 阶段要再遍历一次 fiber 来查找有 effectTag 的节点,更新 dom么?
  • 这样当然没问题,但没必要。齐全能够在 reconcile 的时候把有 effectTag 的节点收集到一个队列里,而后 commit 阶段间接遍历这个队列就行了。
  • 这个队列叫做 effectList
  • react 会在 commit 阶段遍历 effectList,依据 effectTag 来增删改 dom
  • dom 创立前后就是 useEffectuseLayoutEffect 还有一些函数组件的生命周期函数执行的时候。
  • useEffect 被设计成了在 dom 操作前异步调用,useLayoutEffect 是在 dom 操作后同步调用。
  • 为什么这样呢?
  • 因为都要操作 dom 了,这时候如果来了个 effect 同步执行,计算量很大,那不是把 fiber 架构带来的劣势有毁了么?
  • 所以 effect 是异步的,不会阻塞渲染。
  • useLayoutEffect,顾名思义是想在这个阶段拿到一些布局信息的,dom 操作完当前就能够了,而且都渲染完了,天然也就能够同步调用了。
  • 实际上 reactcommit 阶段也分成了 3 个小阶段。
  • before mutationmutationlayout
  • mutation 就是遍历 effectList 来更新 dom 的。
  • 它的之前就是 before mutation,会异步调度 useEffect 的回调函数。
  • 它之后就是 layout 阶段了,因为这个阶段曾经能够拿到布局信息了,会同步调用 useLayoutEffect 的回调函数。而且这个阶段能够拿到新的 dom 节点,还会更新下 ref
  • 至此,咱们对 react 的新架构,rendercommit 两大阶段都干了什么就理清了。

为什么 React 元素有一个 $$typeof 属性

目标是为了避免 XSS 攻打。因为 Synbol 无奈被序列化,所以 React 能够通过有没有 $$typeof 属性来断出以后的 element 对象是从数据库来的还是本人生成的。

  • 如果没有 $$typeof 这个属性,react 会回绝解决该元素。
  • 在 React 的古老版本中,上面的写法会呈现 XSS 攻打:
// 服务端容许用户存储 JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {__html: '/* 把你想的搁着 */'},
  },
  // ...
};
let message = {text: expectedTextButGotJSON};

// React 0.13 中有危险
<p>
  {message.text}
</p>
退出移动版