React使用几种巧妙的技术来最大限度地减少更新UI所需的昂贵的 DOM 操作的数量
1.文件引入大小
下面三种小方法本质都是进行代码的压缩,使文件比较小。
使用生产版本
开发环境我们使用: npm run dev 启动服务。
但是生产版本应使用: npm run build 将代码打包
package.json中:
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --mode development"
},
这样做的好处:1.代码会被压缩 2.引入的包都是压缩后的版本
复制代码
单文件构建
如果使用单文件构建这种方式,应该引入压缩后的版本:
<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
复制代码
webpack
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin()
当使用使用生产版本构建的时候就不需要配置webpack了。
复制代码
2.避免重新渲染
当组件的 props 和 state 改变时,React 通过比较新返回的元素 和 之前渲染的元素 来决定是否有必要更新DOM元素。当二者不相等时,则更新 DOM 元素
scu表示shouldComponentUpdate(组件是否需要更新),红色表示返回true需要更新,绿色表示false不需要更新。当c1需要更新时,查看c2和c3的scu。c2的scu为false,就不需要寻找c4 c5的scu为什么状态。c3的scu为true,查看c6, c7, c8的scu,只有c6的scu为true。所以只需要更新c1,c3, c6。从而避免了其他组件重新渲染。
-
2.1 shouldComponentUpdate
有一个组件功能为:一个输入框,一个添加按钮。输入框输入内容,点击添加按钮。输入框下列表添加上输入框内容。
export default class Todos extends PureComponent {
constructor(props) {
super(props);
this.state = { todos: [] };
}
handleClick = () => {
let todo = this.todo.value;
this.state.todos.push(todo);
this.setState({ todos:this.state.todos });
}
//重写此方法以减少重新渲染
shouldComponentUpdate(nextProps, nextState) {
if (nextState.todos === this.state.todos) {
return false;//老的状态对象和新的状态对象如果是同一个吧,则不用再渲染了
} else {
return true;
}
}
render() {
return (
<div>
<input ref={input => this.todo = input} /><button onClick={this.handleClick}>+</button>
<ul>
{
this.state.todos.map((todo, index) => (<li key={index}>{todo}</li>))
}
</ul>
</div>
)
}
}
复制代码
重写shouldComponentUpdate减少组件的刷新频率:如果我们只需要让todos改变的时候刷新组件,如果todos没有改变就不刷新组件。这样判断减少了组件的刷新次数。
如果没有重写shouldComponentUpdate方法,只要调用了setState,组件就会重新刷新,即使更新内容为空:
handleClick = () => {
this.setState({});
}
//render方法依然被调用,组件依然重新渲染。
复制代码
但是下面这种方法永远不能使组件重新刷新:
handleClick = () => {
let todo = this.todo.value;
this.state.todos.push(todo);
this.setState({ todos:this.state.todos });
}
//重写此方法以减少重新渲染
shouldComponentUpdate(nextProps, nextState) {
if (nextState.todos === this.state.todos) {
return false;//老的状态对象和新的状态对象如果是同一个吧,则不用再渲染了
} else {
return true;
}
}
复制代码
因为:老的数组push一个todo,然后赋值给新的数组。再拿老的数组和新数组比较。比较结果必然相等啊(两个相同地址的对象必然相等)。所以只能判断为相等,不能使组件更新。
与shouldComponentUpdate方法同理,可以使用PureComponent代替Component(就不需要上面原理就是重写shouldComponentUpdate方法方法了)。原理就是重写shouldComponentUpdate方法。
class PureComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
//循环下一个新的属性对象的每一个属性,判断新的属性值和旧的属性是不是同一个
//重点强调 ,这个比较属性属于浅比较
// 如果把这里改为深比较,那么CPU占用高
for (let prop in nextProps) {
if (nextProps[prop] !== this.props[prop]) {
return true;
}
}
for (let prop in nextState) {
if (nextState[prop] !== this.state[prop]) {
return true;
}
}
return false;
}
}
复制代码
解决方法:以上for in 循环比较为浅比较。只是比较的引用地址,如果为一个数组,向数组中添加了一个元素,依然不能比较出改变。再次修改handleClick方法,将todos解构出来,使之不为同一个数组。
handleClick = () => {
let todo = this.todo.value;
this.state.todos.push(todo);
this.setState({ todos:[...this.state.todos] });
}
复制代码
此时面对两个问题:1.如果使用同一个todos,则不能比较出状态的改变。2.如果解构出来一个新的数组,则数组不能复用,重新使用了一个对象,则浪费性能,占用内存(如果todos有100万个)。
有没有两全其美的方法?
import { List } from 'immutable';
handleClick = () => {
let todo = this.todo.value;
let todos = List(this.state.todos);//将老的对象变为immutable对象
todos = todos.push(todo);
this.setState({ todos });
}
复制代码
使用imuutable中的is方法(判断两个对象的属性值是否相等),再次优化shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
return !(is(newProps, this.props) && is(nextState, this.state));
//循环下一个新的属性对象的每一个属性,判断新的属性值和旧的属性是不是同一个
//重点强调 ,这个比较属性属于浅比较
// 如果把这里改为深比较,那么CPU占用高
// for (let prop in nextProps) {
// if (nextProps[prop] !== this.props[prop]) {
// return true;
// }
// }
// for (let prop in nextState) {
// if (nextState[prop] !== this.state[prop]) {
// return true;
// }
// }
// return false;
}
复制代码