共计 12951 个字符,预计需要花费 33 分钟才能阅读完成。
解释 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 在类中不起作用,通过应用它们,咱们能够完全避免应用生命周期办法,例如 componentDidMount
、componentDidUpdate
、componentWillUnmount
。相同,应用像 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 组件生命周期有哪些不同阶段?
在组件生命周期中有四个不同的阶段:
- Initialization:在这个阶段,组件筹备设置初始化状态和默认属性。
- Mounting:react 组件曾经筹备好挂载到浏览器 DOM 中。这个阶段包含
componentWillMount
和componentDidMount
生命周期办法。 - Updating:在这个阶段,组件以两种形式更新,发送新的 props 和 state 状态。此阶段包含
shouldComponentUpdate
、componentWillUpdate
和componentDidUpdate
生命周期办法。 - 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
。如果确定在state
或props
更新后组件不须要在从新渲染,则能够返回false
,这是一个进步性能的办法。componentWillUpdate
:在shouldComponentUpdate
返回true
确定要更新组件之前件之前执行。componentDidUpdate
:它次要用于更新 DOM 以响应props
或state
更改。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 实现原理的了解
简版
react
和vue
都是基于vdom
的前端框架,之所以用vdom
是因为能够精准的比照关怀的属性,而且还能够跨平台渲染- 然而开发不会间接写
vdom
,而是通过jsx
这种靠近html
语法的DSL
,编译产生render function
,执行后产生vdom
vdom
的渲染就是依据不同的类型来用不同的dom api
来操作dom
- 渲染组件的时候,如果是函数组件,就执行它拿到
vdom
。class
组件就创立实例而后调用render
办法拿到vdom
。vue
的那种option
对象的话,就调用render
办法拿到vdom
- 组件实质上就是对一段
vdom
产生逻辑的封装,函数
、class
、option
对象甚至其余模式都能够 react
和vue
最大的区别在状态治理形式上,vue
是通过响应式,react
是通过setState
的api
。我感觉这个是最大的区别,因为它导致了前面react
架构的变更react
的setState
的形式,导致它并不知道哪些组件变了,须要渲染整个vdom
才行。然而这样计算量又会比拟大,会阻塞渲染,导致动画卡顿。所以react
起初革新成了fiber
架构,指标是可打断的计算- 为了这个指标,不能变比照变更新
dom
了,所以把渲染分为了render
和commit
两个阶段,render
阶段通过schedule
调度来进行reconcile
,也就是找到变动的局部,创立dom
,打上增删改的tag
,等全副计算完之后,commit
阶段一次性更新到dom
- 打断之后要找到父节点、兄弟节点,所以
vdom
也被革新成了fiber
的数据结构,有了parent
、sibling
的信息 - 所以
fiber
既指这种链表的数据结构,又指这个render
、commit
的流程 reconcile
阶段每次解决一个fiber
节点,解决前会判断下shouldYield
,如果有更高优先级的工作,那就先执行别的commit
阶段不必再次遍历fiber
树,为了优化,react
把有effectTag
的fiber
都放到了effectList
队列中,遍历更新即可- 在
dom
操作前,会异步调用useEffect
的回调函数,异步是因为不能阻塞渲染 - 在
dom
操作之后,会同步调用useLayoutEffect
的回调函数,并且更新ref
- 所以,
commit
阶段又分成了before mutation
、mutation
、layout
这三个小阶段,就对应下面说的那三局部
了解了
vdom
、jsx
、组件实质
、fiber
、render(reconcile + schedule)
+commit(before mutation、mutation、layout)
的渲染流程,就算是对react
原理有一个比拟深的了解
上面开展剖析
vdom
为什么 react
和 vue
都要基于 vdom
呢?间接操作实在 dom
不行么?
思考下这样的场景:
- 渲染就是用
dom api
对实在dom
做增删改,如果曾经渲染了一个dom
,起初要更新,那就要遍历它所有的属性,从新设置,比方id
、clasName
、onclick
等。 - 而
dom
的属性是很多的:
- 有很多属性基本用不到,但在更新时却要跟着从新设置一遍。
- 能不能只比照咱们关怀的属性呢?
- 把这些独自摘出来用
JS
对象示意不就行了? - 这就是为什么要有
vdom
,是它的第一个益处。 - 而且有了
vdom
之后,就没有和dom
强绑定了,能够渲染到别的平台,比方native
、canvas
等等。 - 这是
vdom
的第二个益处。 - 咱们晓得了
vdom
就是用JS
对象示意最终渲染的dom
的,比方:
{
type: 'div',
props: {
id: 'aaa',
className: ['bbb', 'ccc'],
onClick: function() {}
},
children: []}
而后用渲染器把它渲染进去,然而要让开发去写这样的 vdom
么?那必定不行,这样太麻烦了,大家相熟的是 html
那种形式,所以咱们要引入编译的伎俩
dsl 的编译
dsl
是domain specific language
,畛域特定语言的意思,html
、css
都是web
畛域的dsl
- 间接写
vdom
太麻烦了,所以前端框架都会设计一套dsl
,而后编译成render function
,执行后产生vdom
。 vue
和react
都是这样
这套 dsl 怎么设计呢?前端畛域大家相熟的形容
dom
的形式是html
,最好的形式天然是也设计成那样。所以vue
的template
,react
的jsx
就都是这么设计的。vue
的template compiler
是本人实现的,而react
的jsx
的编译器是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
就是文本,FunctionComponent
、ClassComponent
就别离是函数组件和类组件。那么问题来了,组件怎么渲染呢?这就波及到组件的原理了:
组件
咱们的指标是通过
vdom
形容界面,在react
里会应用jsx
。这样的jsx
有的时候是基于state
来动静生成的。如何把state
和jsx
关联起来呢?封装成function
、class
或者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
,而后递归渲染。所以,大家猜到 vue
的 option
对象的组件形容形式怎么渲染了么?
{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
则是 setState
的 api
的形式。真说起来,vue 和 react 最大的区别就是状态治理形式的区别,因为这个区别导致了前面架构演变方向的不同。
状态治理
react
是通过setState
的api
触发状态更新的,更新当前就从新渲染整个vdom
。而vue
是通过对状态做代理,get
的时候收集以来,而后批改状态的时候就能够触发对应组件的render
了。
有的同学可能会问,为什么 react
不间接渲染对应组件呢?
设想一下这个场景:
父组件把它的 setState
函数传递给子组件,子组件调用了它。这时候更新是子组件触发的,然而要渲染的就只有那个组件么?显著不是,还有它的父组件。同理,某个组件更新实际上可能触发任意地位的其余组件更新的。所以必须从新渲染整个 vdom
才行。
那 vue
为啥能够做到精准的更新变动的组件呢?因为响应式的代理呀,不论是子组件、父组件、还是其余地位的组件,只有用到了对应的状态,那就会被作为依赖收集起来,状态变动的时候就能够触发它们的 render
,不论是组件是在哪里的。这就是为什么 react
须要从新渲染整个 vdom
,而 vue
不必。这个问题也导致了起初两者架构上逐步有了差别。
react 架构的演变
react15
的时候,和vue
的渲染流程还是很像的,都是递归渲染vdom
,增删改dom
就行。然而因为状态治理形式的差别逐步导致了架构的差别。react
的setState
会渲染整个vdom
,而一个利用的所有vdom
可能是很宏大的,计算量就可能很大。浏览器里js
计算工夫太长是会阻塞渲染的,会占用每一帧的动画、重绘重排的工夫,这样动画就会卡顿。作为一个有谋求的前端框架,动画卡顿必定是不行的。然而因为setState
的形式只能渲染整个vdom
,所以计算量大是不可避免的。那能不能把计算量拆分一下,每一帧计算一部分,不要阻塞动画的渲染呢?顺着这个思路,react
就革新为了fiber
架构。
fiber 架构
优化的指标是打断计算,分屡次进行,但当初递归的渲染是不能打断的,有两个方面的起因导致的:
- 渲染的时候间接就操作了 dom 了,这时候打断了,那曾经更新到 dom 的那局部怎么办?
- 当初是间接渲染的 vdom,而 vdom 里只有 children 的信息,如果打断了,怎么找到它的父节点呢?
第一个问题的解决还是容易想到的:
- 渲染的时候不要间接更新到
dom
了,只找到变动的局部,打个增删改的标记,创立好dom
,等全副计算完了一次性更新到dom
就好了。 - 所以
react
把渲染流程分为了两局部:render
和commit
。 render
阶段会找到vdom
中变动的局部,创立dom
,打上增删改的标记,这个叫做reconcile
,和谐。reconcile
是能够打断的,由schedule
调度。- 之后全副计算完了,就一次性更新到
dom
,叫做commit
。 - 这样,
react
就把之前的和vue
很像的递归渲染,革新成了render(reconcile + schdule)+ commit
两个阶段的渲染。 - 从此以后,
react
和vue
架构上的差别才大了起来。
第二个问题,如何打断当前还能找到父节点、其余兄弟节点呢?
现有的 vdom
是不行的,须要再记录下 parent
、silbing
的信息。所以 react
发明了 fiber
的数据结构。
- 除了
children
信息外,额定多了sibling
、return
,别离记录着兄弟节点、父节点的信息。 - 这个数据结构也叫做
fiber
。(fiber
既是一种数据结构,也代表render + commit
的渲染流程)react
会先把vdom
转换成fiber
,再去进行reconcile
,这样就是可打断的了。 - 为什么这样就能够打断了呢?因为当初不再是递归,而是循环了:
function workLoop() {while (wip) {performUnitOfWork();
}
if (!wip && wipRoot) {commitRoot();
}
}
react
里有一个 workLoop 循环,每次循环做一个fiber
的reconcile
,以后解决的fiber
会放在workInProgress
这个全局变量上。- 当循环完了,也就是
wip
为空了,那就执行commit
阶段,把reconcile
的后果更新到dom
。 - 每个
fiber
的reconcile
是依据类型来做的不同解决。当解决完了以后fiber
节点,就把wip
指向sibling
、return
来切到下个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
创立前后就是useEffect
、useLayoutEffect
还有一些函数组件的生命周期函数执行的时候。useEffect
被设计成了在dom
操作前异步调用,useLayoutEffect
是在dom
操作后同步调用。- 为什么这样呢?
- 因为都要操作
dom
了,这时候如果来了个effect
同步执行,计算量很大,那不是把 fiber 架构带来的劣势有毁了么? - 所以
effect
是异步的,不会阻塞渲染。 - 而
useLayoutEffect
,顾名思义是想在这个阶段拿到一些布局信息的,dom 操作完当前就能够了,而且都渲染完了,天然也就能够同步调用了。 - 实际上
react
把commit
阶段也分成了3
个小阶段。 before mutation
、mutation
、layout
。mutation
就是遍历effectList
来更新dom
的。- 它的之前就是
before mutation
,会异步调度useEffect
的回调函数。 - 它之后就是
layout
阶段了,因为这个阶段曾经能够拿到布局信息了,会同步调用useLayoutEffect
的回调函数。而且这个阶段能够拿到新的dom
节点,还会更新下ref
。 - 至此,咱们对
react
的新架构,render
、commit
两大阶段都干了什么就理清了。
为什么 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>