- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解State和Props,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。
React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX、理解State
和Props
,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你有用。
这是Choerodon的一个前端页面
在复杂的前端项目中一个页面可能包含上百个状态,对React框架理解得更精细一些对前端优化很重要。曾经这个页面点击一条记录展示详情会卡顿数秒,而这仅仅是前端渲染造成的。
为了能够解决这些问题,开发者需要了解React组件从定义到在页面上呈现(然后更新)的整个过程。
React在编写组件时使用混合HTML
和JavaScript
的一种语法(称为JSX)。 但是,浏览器对JSX及其语法一无所知,浏览器只能理解纯JavaScript
,因此必须将JSX转换为HTML
。 这是一个div的JSX代码,它有一个类和一些内容:
<div className='cn'>
文本
</div>
在React中将这段jsx变成普通的js之后它就是一个带有许多参数的函数调用:
React.createElement(
'div',
{ className: 'cn' },
'文本'
);
React.createElement(
'div',
{ className: 'cn' },
['Content 1!', React.createElement('br'), 'Content 2!']
)
它的第一个参数是一个字符串,对应html中的标签名,第二个参数是它的所有属性所构成的对象,当然,它也有可能是个空对象,剩下的参数都是这个元素下的子元素,这里的文本也会被当作一个子元素,所以第三个参数是 `“文本”` 。
到这里你应该就能想象这个元素下有更多`children`的时候会发生什么。
```html
<div className='cn'>
文本1
<br />
文本2
</div>
React.createElement(
'div',
{ className: 'cn' },
'文本1', // 1st child
React.createElement('br'), // 2nd child
'文本1' // 3rd child
)
目前的函数有五个参数:元素的类型,全部属性的对象和三个子元素。 由于一个child
也是React已知的HTML
标签,因此它也将被解释成函数调用。
到目前为止,本文已经介绍了两种类型的child
参数,一种是string
纯文本,一种是调用其他的React.createElement
函数。其实,其他值也可以作为参数,比如: - 基本类型 false,null,undefined和 true - 数组 - React组件
使用数组是因为可以将子组件分组并作为一个参数传递:
当然,React的强大功能不是来自`HTML`规范中描述的标签,而是来自用户创建的组件,例如:
```js
function Table({ rows }) {
return (
<table>
{rows.map(row => (
<tr key={row.id}>
<td>{row.title}</td>
</tr>
))}
</table>
);
}
组件允许开发者将模板分解为可重用的块。在上面的“纯函数”组件的示例中,组件接受一个包含表行数据的对象数组,并返回React.createElement
对table元素及其行作为子元素的单个调用 。
每当开发者将组件放入JSX布局中时它看上去是这样的:
<Table rows={rows} />
但从浏览器角度,它看到的是这样的:
React.createElement(Table, { rows: rows });
请注意,这次的第一个参数不是以string
描述的HTML元素,而是组件的引用(即函数名)。第二个参数是传入该组件的props
对象。
现在,浏览器已经将所有JSX组件转换为纯JavaScript
,现在浏览器获得了一堆函数调用,其参数是其他函数调用,还有其他函数调用……如何将它们转换为构成网页的DOM元素?
为此,开发者需要使用ReactDOM
库及其render
方法:
function Table({ rows }) { /* ... */ } // 组件定义
// 渲染一个组件
ReactDOM.render(
React.createElement(Table, { rows: rows }), // "创建" 一个 component
document.getElementById('#root') // 将它放入DOM中
);
当ReactDOM.render
被调用时,React.createElement
最终也会被调用,它返回以下对象:
// 这个对象里还有很多其他的字段,但现在对开发者来说重要的是这些。
{
type: Table,
props: {
rows: rows
},
// ...
}
这些对象构成了React意义上的Virtual DOM
它们将在所有进一步渲染中相互比较,并最终转换为真正的DOM(与Virtual DOM对比)。
这是另一个例子:这次有一个div具有class属性和几个子节点:
React.createElement(
'div',
{ className: 'cn' },
'Content 1!',
'Content 2!',
);
变成:
{
type: 'div',
props: {
className: 'cn',
children: [
'Content 1!',
'Content 2!'
]
}
}
所有的传入的展开函数,也就是React.createElement
除了第一第二个参数剩下的参数都会在props
对象中的children
属性中,不管传入的是什么函数,他们最终都会作为children
传入props
中。
而且,开发者可以直接在JSX代码中添加children
属性,将子项直接放在children
中,结果仍然是相同的:
<div className='cn' children={['Content 1!', 'Content 2!']} />
在Virtual DOM对象被建立出来之后ReactDOM.render
会尝试按以下规则把它翻译成浏览器能够看得懂的DOM节点: - 如果
Virtual DOM对象中的type属性是一个string类型的tag名称,就
创建一个tag,包含props里的全部属性。 - 如果
Virtual DOM对象中的type属性是一个函数或者class,就
调用它,它返回的可能还是一个Virtual DOM然后将结果继续递归调用此过程。 - 如果
props中有children属性,就
对children中的每个元素进行以上过程,并将返回的结果放到父DOM节点中。
最后,浏览器获得了以下HTML(对于上述table的例子):
<table>
<tr>
<td>Title</td>
</tr>
...
</table>
接下浏览器要“重建”一个DOM节点,如果浏览器要更新一个页面,显然,开发者并不希望替换页面中的全部元素,这就是React真正的魔法了。如何才能实现它?先从最简单的方法开始,重新调用这个节点的ReactDOM.render
方法。
// 第二次调用
ReactDOM.render(
React.createElement(Table, { rows: rows }),
document.getElementById('#root')
);
这一次,上面的代码执行逻辑将与看到的代码不同。React不是从头开始创建所有DOM节点并将它们放在页面上,React将使用“diff”算法,以确定节点树的哪些部分必须更新,哪些部分可以保持不变。
那么它是怎样工作的?只有少数几个简单的情况,理解它们将对React程序的优化有很大帮助。请记住,接下来看到的对象是用作表示React Virtual DOM中节点的对象。
▌Case 1:type是一个字符串,type在调用之间保持不变,props也没有改变。
// before update
{ type: 'div', props: { className: 'cn' } }
// after update
{ type: 'div', props: { className: 'cn' } }
这是最简单的情况:DOM保持不变。
▌Case 2:type仍然是相同的字符串,props是不同的。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'div', props: { className: 'cnn' } }
由于type仍然代表一个HTML元素,React知道如何通过标准的DOM API调用更改其属性,而无需从DOM树中删除节点。
▌Case 3:type已更改为不同的组件String或从String组件更改为组件。
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'span', props: { className: 'cn' } }
由于React现在看到类型不同,它甚至不会尝试更新DOM节点:旧元素将与其所有子节点一起被删除(unmount)。因此,在DOM树上替换完全不同的元素的代价会非常之高。幸运的是,这在实际情况中很少发生。
重要的是要记住React使用===(三等)来比较type值,因此它们必须是同一个类或相同函数的相同实例。
下一个场景更有趣,因为这是开发者最常使用React的方式。
▌Case 4:type是一个组件。
// before update:
{ type: Table, props: { rows: rows } }
// after update:
{ type: Table, props: { rows: rows } }
你可能会说,“这好像没有任何变化”,但这是不对的。
如果type是对函数或类的引用(即常规React组件),并且启动了树diff比较过程,那么React将始终尝试查看组件内部的所有child
以确保render
的返回值没有更改。即在树下比较每个组件 - 是的,复杂的渲染也可能变得昂贵!
除了上面描述的四种常见场景之外,当元素有多个子元素时,开发者还需要考虑React的行为。假设有这样一个元素:
// ...
props: {
children: [
{ type: 'div' },
{ type: 'span' },
{ type: 'br' }
]
},
// ...
开发者开发者想将它重新渲染成这样(span
和div
交换了位置):
// ...
props: {
children: [
{ type: 'span' },
{ type: 'div' },
{ type: 'br' }
]
},
// ...
那么会发生什么?
当React看到里面的任何数组类型的props.children
,它会开始将它中的元素与之前看到的数组中的元素按顺序进行比较:index 0将与index 0,index 1与index 1进行比较,对于每对子元素,React将应用上述规则集进行比较更新。在以上的例子中,它看到div
变成一个span
这是一个情景3中的情况。但这有一个问题:假设开发者想要从1000行表中删除第一行。React必须“更新”剩余的999个孩子,因为如果与先前的逐个索引表示相比,他们的内容现在将不相等。
幸运的是,React有一种内置的方法来解决这个问题。如果元素具有key
属性,则元素将通过key
而不是索引进行比较。只要key
是唯一的,React就会移动元素而不将它们从DOM树中移除,然后将它们放回(React中称为挂载/卸载的过程)。
// ...
props: {
children: [ // 现在react就是根据key,而不是索引来比较了
{ type: 'div', key: 'div' },
{ type: 'span', key: 'span' },
{ type: 'br', key: 'bt' }
]
},
// ...
到目前为止,本文只触及了props
,React哲学的一部分,但忽略了state
。这是一个简单的“有状态”组件:
class App extends Component {
state = { counter: 0 }
increment = () => this.setState({
counter: this.state.counter + 1,
})
render = () => (<button onClick={this.increment}>
{'Counter: ' + this.state.counter}
</button>)
}
现在,上述例子中的state
对象有一个counter
属性。单击按钮会增加其值并更改按钮文本。但是当用户点击时,DOM会发生什么?它的哪一部分将被重新计算和更新?
调用this.setState
也会导致重新渲染,但不会导致整个页面重渲染,而只会导致组件本身及其子项。父母和兄弟姐妹都可以幸免于难。
本文准备了一个DEMO,这是修复问题前的样子。你可以在这里查看其源代码。不过在此之前,你还需要安装React Developer Tools。
打开demo要看的第一件事是哪些元素以及何时导致Virtual DOM更新。导航到浏览器的Dev Tools中的React面板,点击设置然后选择“Highlight Updates”复选框:
现在尝试在表中添加一行。如你所见,页面上的每个元素周围都会出现边框。这意味着每次添加行时,React都会计算并比较整个Virtual DOM树。现在尝试按一行内的计数器按钮。你将看到Virtual DOM如何更新 (state仅相关元素及其子元素更新)。
React DevTools暗示了问题可能出现的地方,但没有告诉开发者任何细节:特别是有问题的更新是指元素“diff”之后有不同,还是组件被unmount/mount了。要了解更多信息,开发者需要使用React的内置分析器(请注意,它不能在生产模式下工作)。
转到Chrome DevTools中的“Performance”标签。点击record按钮,然后点击表格。添加一些行,更改一些计数器,然后点击“Stop”按钮。稍等一会儿之后开发者会看到:
在结果输出中,开发者需要关注“Timing”。缩放时间轴,直到看到“React Tree Reconciliation”组及其子项。这些都是组件的名称,旁边有[update]或[mount]。可以看到有一个TableRow被mount了,其他所有的TableRow都在update,这并不是开发者想要的。
大多数性能问题都由[update]或[mount]引起
一个组件(以及组件下的所有东西)由于某种原因在每次更新时重新挂载,开发者不想让它发生(重新挂载很慢),或者在大型分支上执行代价过大的重绘,即使组件似乎没有发生任何改变。
现在,当开发者了解React如何决定更新Virtual DOM并知道幕后发生的事情时,终于准备好解决问题了!修复性能问题首先要解决 mount/unmount。
如果开发者将任何元素/组件的多个子元素在内部表示为数组,那么程序可以获得非常明显的速度提升。
考虑一下:
<div>
<Message />
<Table />
<Footer />
</div>
在虚拟DOM中,将表示为:
// ...
props: {
children: [
{ type: Message },
{ type: Table },
{ type: Footer }
]
}
// ...
一个简单的Message
组件(是一个div
带有一些文本,像是猪齿鱼的顶部通知)和一个很长的Table
,比方说1000多行。它们都是div
元素的child
,因此它们被放置在父节点的props.children
之下,并且它们没有key
。React甚至不会通过控制台警告来提醒开发者分配key,因为子节点React.createElement
作为参数列表而不是数组传递给父节点。
现在,用户已经关闭了顶部通知,所以Message
从树中删除。Table
、Footer
是剩下的child。
// ...
props: {
children: [
{ type: Table },
{ type: Footer }
]
}
// ...
React如何看待它?它将它视为一系列改变了type的child:children[0]的type本来是Message
,但现在他是Table
。因为它们都是对函数(和不同函数)的引用,它会卸载整个Table并再次安装它,渲染它的所有子代:1000多行!
因此,你可以添加唯一键(但在这种特殊情况下使用key
不是最佳选择)或者采用更智能的trick:使用 && 的布尔短路运算,这是JavaScript
和许多其他现代语言的一个特性。像这样:
<div>
{isShowMessage && <Message />}
<Table />
<Footer />
</div>
即使Message
被关闭了(不再显示),props.children
父母div仍将拥有三个元素,children[0]具有一个值false
(布尔类型)。还记得true
/false
, null
甚至undefined
都是Virtual DOM对象type属性的允许值吗?浏览器最终得到类似这样的东西:
// ...
props: {
children: [
false, // isShowMessage && <Message /> 短路成了false
{ type: Table },
{ type: Footer }
]
}
// ...
所以,不管Message
是否被显示,索引都不会改变,Table
仍然会和Table
比较,但仅仅比较Virtual DOM通常比删除DOM节点并从中创建它们要快得多。
现在来看看更高级的东西。开发者喜欢HOC。高阶组件是一个函数,它将一个组件作为一个参数,添加一些行为,并返回一个不同的组件(函数):
function withName(SomeComponent) {
return function(props) {
return <SomeComponent {...props} name={name} />;
}
}
开发者在父render
方法中创建了一个HOC 。当React
需要重新渲染树时,React
的Virtual DOM将如下所示:
// On first render:
{
type: ComponentWithName,
props: {},
}
// On second render:
{
type: ComponentWithName, // Same name, but different instance
props: {},
}
现在,React只会在ComponentWithName上运行一个diff算法,但是这次同名引用了一个不同的实例,三等于比较失败,必须进行完全重新挂载。注意它也会导致状态丢失,幸运的是,它很容易修复:只要返回的实例都是同一个就好了:
// 单例
const ComponentWithName = withName(Component);
class App extends React.Component() {
render() {
return <ComponentWithName />;
}
}
现在浏览器已经确保不会重新装载东西了,除非必要。但是,对位于DOM树根目录附近的组件所做的任何更改都将导致其所有子项的进行对比重绘。结构复杂,价格昂贵且经常可以避免。
如果有办法告诉React不要查看某个分支,那将是很好的,因为它没有任何变化。
这种方式存在,它涉及一个叫shouldComponentUpdate
的组件生命周期函数。React会在每次调用组件之前调用此方法,并接收props
和state
的新值。然后开发者可以自由地比较新值和旧值之间的区别,并决定是否应该更新组件(返回true
或false
)。如果函数返回false
,React将不会重新渲染有问题的组件,也不会查看其子组件。
通常比较两组props
和state
一个简单的浅层比较就足够了:如果顶层属性的值相同,浏览器就不必更新了。浅比较不是JavaScript
的一个特性,但开发者很多方法来自己实现它,为了不重复造轮子,也可以使用别人写好的方法。
在引入浅层比较的npm包后,开发者可以编写如下代码:
class TableRow extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const { props, state } = this;
return !shallowequal(props, nextProps)
&& !shallowequal(state, nextState);
}
render() { /* ... */ }
}
但是你甚至不必自己编写代码,因为React在一个名为React.PureComponent的类中内置了这个功能,它类似于React.Component
,只是shouldComponentUpdate
已经为你实现了浅层props/state比较。
或许你会有这样的想法,能替换Component
为PureComponent
就去替换。但开发者如果错误地使用PureComponent
同样会有重新渲染的问题存在,需要考虑下面三种情况:
<Table
// map每次都会返回一个新的数组实例,所以每次比较都是不同的
rows={rows.map(/* ... */)}
// 每一次传入的对象都是新的对象,引用是不同的。
style={ { color: 'red' } }
// 箭头函数也一样,每次都是不同的引用。
onUpdate={() => { /* ... */ }}
/>
上面的代码片段演示了三种最常见的反模式,请尽量避免它们!
正确地使用PureComponent
,你可以在这里看到所有的TableRow都被“纯化”后渲染的效果。
但是,如果你迫不及待想要全部使用纯函数组件,这样是不对的。比较两组props
和state
不是免费的,对于大多数基本组件来说甚至都不值得:运行shallowCompare
比diff算法需要更多时间。
可以使用此经验法则:纯组件适用于复杂的表单和表格,但它们通常会使按钮或图标等简单元素变慢。
现在,你已经熟悉了React的渲染模式,接下来就开始前端优化之旅吧。
本文由猪齿鱼技术团队原创,转载请注明出处
问题是,当用户回复彼此的帖子时,我必须这样做: margin-left:40px; 对于 1 级深度 react margin-left:80px; 对于 2 层深等 但是我想让 react div
我试图弄清楚如何将 React Router 与 React VR 连接起来。 首先,我应该使用 react-router dom/native ?目前尚不清楚,因为 React VR 构建在 Rea
我是 React 或一般编码背景的新手。我不确定这些陈述之间有什么区别 import * as react from 'react' 和 import react from 'react' 提前致谢!
我正在使用最新的稳定版本的 react、react-native、react-test-renderer、react-dom。 然而,react-native 依赖于 react@16.0.0-alp
是否 react 原生 应用程序开发可以通过软件架构实现,例如 MVC、MVP、MVVM ? 谢谢你。 最佳答案 是的。 React Native 只是你提到的那些软件设计模式中的“V”。如果你考虑其
您好我正在尝试在我的导航器右按钮中绑定(bind)一个功能, 但它给出了错误。 这是我的代码: import React, { Component } from 'react'; import Ico
我使用react native创建了一个应用程序,我正在尝试生成apk。在http://facebook.github.io/react-native/docs/signed-apk-android.
1 [我尝试将分页的 z-index 更改为 0,但没有成功] 这是我的codesandbox的链接:请检查最后一个选择下拉列表,它位于分页后面。 https://codesandbox.io/s/j
我注意到 React 可以这样导入: import * as React from 'react'; ...或者像这样: import React from 'react'; 第一个导入 react
我是 react-native 的新手。我正在使用 React Native Paper 为所有屏幕提供主题。我也在使用 react 导航堆栈导航器和抽屉导航器。首先,对于导航,论文主题在导航组件中不
我有一个使用 Ignite CLI 创建的 React Native 应用程序.我正在尝试将 TabNavigator 与 React Navigation 结合使用,但我似乎无法弄清楚如何将数据从一
我正在尝试在我的 React 应用程序中进行快照测试。我已经在使用 react-testing-library 进行一般的单元测试。然而,对于快照测试,我在网上看到了不同的方法,要么使用 react-
我正在使用 react-native 构建跨平台 native 应用程序,并使用 react-navigation 在屏幕之间导航和使用 redux 管理导航状态。当我嵌套导航器时会出现问题。 例如,
由于分页和 React Native Navigation,我面临着一种复杂的问题。 单击具有类别列表的抽屉,它们都将转到屏幕 问题陈述: 当我随机点击类别时,一切正常。但是,在分页过程中遇到问题。假
这是我的抽屉导航: const DashboardStack = StackNavigator({ Dashboard: { screen: Dashboard
尝试构建 react-native android 应用程序但出现以下错误 info Running jetifier to migrate libraries to AndroidX. You ca
我目前正在一个应用程序中实现 React Router v.4,我也在其中使用 Webpack 进行捆绑。在我的 webpack 配置中,我将 React、ReactDOM 和 React-route
我正在使用 React.children 渲染一些带有 react router 的子路由(对于某个主路由下的所有子路由。 这对我来说一直很好,但是我之前正在解构传递给 children 的 Prop
当我运行 React 应用程序时,它显示 export 'React'(导入为 'React')在 'react' 中找不到。所有页面错误 see image here . 最佳答案 根据图像中的错误
当我使用这个例子在我的应用程序上实现 Image-slider 时,我遇到了这个错误。 import React,{Component} from 'react' import {View,T
我是一名优秀的程序员,十分优秀!