我是react JS 的新手。任何人都可以确切地解释和解它是如何工作的。我曾尝试从 react 官方网站上理解它,但没有理解。
React 中的和解详解
这就是我的理解:
你会同意 react 使用 components 使事情变得简单和快速。使用 JSX,我们可以让用户定义的组件变得更容易。一天结束时,所有这些都被转换为纯 JavaScript(我假设您了解 React.createElement 的工作原理),函数调用将其他函数调用作为其参数/属性,并保存其他函数调用等等。无论如何,我们没有什么可做的担心react会在内部自行完成。
但这如何为我们提供 UI 呢?为什么它比其他 UI 库更快?
<-- ALL HAIL ReactDOM 库和渲染方法 -->
一个普通的 ReactDOM 调用看起来像这样:
// I have avoided the usage of JSX as its get transpiled anyway
ReactDOM.render(
React.createElement(App, { //if any props to pass or child }), // "creating" a component
document.getElementById('#root') // inserting it on a page
);
Heard about VirtualDOM ? { yes : 'Good'} : { no : 'still Good'} ;
React.createElement 根据我们编写的组件构造具有类型和props的元素对象,并将子元素放置在props内的子键下。它以递归方式执行此操作并填充一个最终对象,该对象已准备好转换为等效的 HTML 并绘制到浏览器。
这就是 VirtualDOM,它驻留在 reacts 内存中,react 在这个内存上执行所有操作,而不是在实际的 Browser DOM 上执行。它看起来像这样:
{
type: 'div',// could be other html'span' or user-diff 'MyComponent'
props: {
className: 'cn',
//other props ...
children: [
'Content 1!', // could be a component itself
'Content 2!', // could be a component itself
'Content n!', // could be a component itself
]
}
}
构建虚拟 DOM 对象后,ReactDOM.render 会将其转换为我们的浏览器可以根据这些规则绘制 UI 的 DOM 节点:
如果一个类型属性包含一个带有标签名称的字符串——创建一个带有在 props 下列出的所有属性的标签。如果我们在 type 下有一个函数或一个类——调用它并递归地对结果重复这个过程。如果 props 下有任何子节点——对每个子节点一一重复该过程并将结果放置在父节点的 DOM 节点中。
浏览器将其绘制到 UI,这是一项昂贵的任务。React 非常聪明地理解这一点。更新组件意味着创建一个新对象并绘制到 UI。即使涉及一个小的更改,它也会重新创建整个 DOM 树。那么我们如何让浏览器不必每次都创建 DOM 而只绘制必要的东西。
这是我们需要 Reconciliation 和 React 的 diffing 算法的地方 .. 多亏了 React,我们不必自己手动完成它,它在内部得到了照顾,这是一篇很好的文章,可以更深入地理解
现在你甚至可以参考官方的Reconsiliation 文档
值得注意的几点:
React 基于两个假设实现启发式 O(n) 算法:1) 不同类型的两个元素将生成不同的树。2) 开发人员可以通过 key prop 暗示哪些子元素在不同的渲染中可能是稳定的。
在实践中,这些假设几乎适用于所有实际用例。如果不满足这些,则会导致性能问题。
我只是复制粘贴其他几点只是为了了解它是如何完成的:
Diffing :当比较两棵树时,React 首先比较两个根元素。行为因根元素的类型而异。
场景 1:type 是一个字符串,type 在调用中保持不变,props 也没有改变。
// before update
{ type: 'div', props: { className: 'cn' , title : 'stuff'} }
// after update
{ type: 'div', props: { className: 'cn' , title : 'stuff'} }
这是最简单的情况:DOM 保持不变。
场景二:type还是一样的string,props不同。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'div', props: { className: 'cnn' } }
由于 type 仍然代表一个 HTML 元素,React 会查看两者的属性,React 知道如何通过标准 DOM API 调用更改其属性,而无需从 DOM 树中删除底层 DOM 节点。
React 也知道只更新改变的属性。例如:
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
在这两个元素之间转换时,React 知道只修改颜色样式,而不是 fontWeight。
///////当组件更新时,实例保持不变,以便在渲染之间保持状态。React 更新底层组件实例的 props 以匹配新元素,并在底层实例上调用 componentWillReceiveProps() 和 componentWillUpdate()。接下来,render() 方法被调用,diff 算法对先前的结果和新的结果进行递归。处理完 DOM 节点后,React 会在子节点上递归。
场景三:type 变成了不同的 String,或者从 String 变成了一个组件。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'span', props: { className: 'cn' } }
由于 React 现在看到类型不同,它甚至不会尝试更新我们的节点:旧元素将与其所有子元素一起被删除(卸载)。
记住 React 使用 === (triple equals) 来比较类型值很重要,所以它们必须是同一个类或同一个函数的相同实例。
场景4:类型是一个组件。
// before update:
{ type: Table, props: { rows: rows } }
// after update:
{ type: Table, props: { rows: rows } }
“但什么都没有改变!”,你可能会说,那你就错了。
如果 type 是对函数或类(即您的常规 React 组件)的引用,并且我们开始了树协调过程,那么 React 将始终尝试查看组件内部以确保渲染时返回的值没有改变(某种预防副作用的预防措施)。冲洗并重复树下的每个组件 - 是的,复杂的渲染也可能变得昂贵!
为了确保这些事情变得干净:
class App extends React.Component {
state = {
change: true
}
handleChange = (event) => {
this.setState({change: !this.state.change})
}
render() {
const { change } = this.state
return(
<div>
<div>
<button onClick={this.handleChange}>Change</button>
</div>
{
change ?
<div>
This is div cause it's true
<h2>This is a h2 element in the div</h2>
</div> :
<p>
This is a p element cause it's false
<br />
<span>This is another paragraph in the false paragraph</span>
</p>
}
</div>
)
}
}
孩子们==============================>
当一个元素有多个子元素时,我们还需要考虑 React 的行为。假设我们有这样一个元素:
// ...
props: {
children: [
{ type: 'div' },
{ type: 'span' },
{ type: 'br' }
]
},
// ...
我们想把这些孩子洗牌:
// ...
props: {
children: [
{ type: 'span' },
{ type: 'div' },
{ type: 'br' }
]
},
// ...
那会发生什么?
如果,在“diffing”时,React 在 props.children 中看到任何数组,它会开始将其中的元素与它之前看到的数组中的元素进行比较,按顺序查看它们:索引 0 将与索引 0 进行比较,索引 1 到索引 1 等。对于每一对,React 将应用上述规则集。
React 有一个内置的方法来解决这个问题。如果元素具有键属性,则元素将通过键的值而不是索引进行比较。只要键是唯一的,React 就会四处移动元素,而不会将它们从 DOM 树中移除,然后将它们放回去(在 React 中称为挂载/卸载的过程)。
所以密钥应该是稳定的、可预测的和唯一的。不稳定的键(如 Math.random() 生成的键)将导致不必要地重新创建许多组件实例和 DOM 节点,这可能导致子组件的性能下降和状态丢失。
因为 React 依赖于启发式,如果不满足它们背后的假设,性能就会受到影响。
当状态改变时: ==========================================>
调用 this.setState 也会导致重新渲染,但不是整个页面的重新渲染,而是组件本身及其子组件的重新渲染。父母和兄弟姐妹幸免于难。当我们有一棵大树并且我们只想重绘它的一部分时,这很方便。
React 上下文中的 Reconciliation 是指让 React 的虚拟 DOM 树与浏览器的真实 DOM 树保持一致。这发生在(重新)渲染期间
关键是不能保证 React 虚拟 DOM 的特定元素在其整个生命周期中引用浏览器的同一个 DOM 节点。这样做的原因是 React 有效更新 DOM 的方法。key
如果组件包含动态或有状态的子组件,您可以使用特殊属性来解决此问题。