备战秋招,复习基础。如有错误,欢迎批评指正,共同进步!
基本概念
声明式:创建用户交互界面,高效渲染页面。声明式编写UI方便调试。
组件化:使用组件传递数据,将应用状态和DOM拆分开。当内部状态(this.state)改变时,组件会调用方法(render())重新渲染。
箭头函数:var func = (x,y) => {return x+y;};
class → className for → htmlFor
样式 style需要{{}}
JSX:很像XML的js语法扩展。可用{js表达式} → 没有if else,只有三元表达式
Key:每次构建动态列表的时候,指定一个合适的key。在同一级元素之间保证唯一!
组件API:
- setState 设置状态 每次调用时会自动更新子组件,但不是立即生效!
- replaceState 替换状态
- setProps 设置属性
- replaceProps 替换属性
- forceUpdate 强制更新
- findDOMNode 获取DOM节点
- isMounted 判断组件挂载状态
实现原理
参考资料:react基本原理及性能优化
react只负责解决view层的渲染
虚拟DOM
参考资料:浅谈React的最大亮点——虚拟DOM
虚拟DOM是在DOM的基础上建立了一个抽象层,是一个js对象。对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中。
虚拟DOM可以确保只对界面上真正变化的部分进行实际的DOM操作。
真实的DOM tree结构:
<div id="container">
<p>Real DOM</p>
<ul>
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
</div>
虚拟DOM结构:
let virtualDomTree = CreateElement('div', { id: 'container' }, [
CreateElement('p', {}, ['Virtual DOM']),
CreateElement('ul', {}, [
CreateElement('li', { class: 'item' }, ['Item 1']),
CreateElement('li', { class: 'item' }, ['Item 2']),
CreateElement('li', { class: 'item' }, ['Item 3']),
]),
]);
let root = virtualDomTree.render(); //转换为一个真正的dom结构或者dom fragment
document.getElementById('virtualDom').appendChild(root);
function CreateElement(tagName, props, children) {
if (!(this instanceof CreateElement)) {
return new CreateElement(tagName, props, children);
}
this.tagName = tagName;
this.props = props || {};
this.children = children || [];
this.key = props ? props.key : undefined;
}
CreateElement.prototype.render = function() {
let el = document.createElement(this.tagName);
let props = this.props;
for (let propName in props) {
setAttr(el, propName, props[propName]);
}
this.children.forEach((child) => {
let childEl = (child instanceof Element) ? child.render() : document.createTextNode(child);
el.appendChild(childEl);
});
return el;
};
diff算法
参考资料:React之diff算法
计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。
- react diff算法制定了三条策略,将算法复杂度从 O(n3)降低到O(n)。
- WebUI中DOM节点跨节点的操作特别少,可以忽略不计。
- 拥有相同类的组件会拥有相似的DOM结构。拥有不同类的组件会生成不同的DOM结构。
- 同一层级的子节点,可以根据唯一的ID来区分。
- react diff实施的具体策略是:
-
diff对树进行分层比较,只对比两棵树同级别的节点。跨层级移动节点,将会导致节点删除,重新插入,无法复用。
-
diff对组件进行类比较,类相同的递归diff子节点,不同的直接销毁重建。diff对同一层级的子节点进行处理时,会根据key进行简要的复用。两棵树中存在相同key的节点时,只会移动节点。
-
在对比同一层级的子节点时,diff算法会以新树的第一个子节点作为起点遍历新树,寻找旧树中与之相同的节点。如果节点存在,则移动位置。如果不存在,则新建一个节点。 在这过程中,维护了一个字段lastIndex,这个字段表示已遍历的所有新树子节点在旧树中最大的index。在移动操作时,只有旧index小于lastIndex的才会移动。 这个顺序优化方案实际上是基于一个假设,大部分的列表操作应该是保证列表基本有序的。 可以推倒倒序的情况下,子节点列表diff的算法复杂度为O(n2)
生命周期
- 初始化
getDefaultProps()设置默认的propsgetInitialState()定义初始this.statecomponentWillMount()修改state的最后一次机会render()创建一个虚拟DOM,表示组件的输出(唯一必须的方法)componentDidMount()访问真实DOM,进行数据监听、数据请求
- 更新
componentWillReceiveProps()可更新state,触发rendershouldComponentUpdate()决定是否需要重新渲染,优化渲染性能componentWillUpdate()重新渲染前调用componentDidUpdate()访问并修改DOM,监听props或state变化。这里放setState必须加条件,否则会导致死循环。
- 销毁
componentWillUnmount()将组件从DOM中卸载并销毁`
setState
-
理想情况:
setState是“异步”的,调用setState只会提交一次state修改到队列中,不会直接修改this.state。 等到满足一定条件时,react会合并队列中的所有修改,触发一次update流程,更新this.state。
因此setState机制减少了update流程的触发次数,从而提高了性能。
由于setState会触发update过程,因此在update过程中必经的生命周期中调用setState会存在循环调用的风险。
另外用于监听state更新完成,可以使用setState方法的第二个参数,回调函数。在这个回调中读取this.state就是已经批量更新后的结果。
-
特殊情况:在实际开发中,setState的表现有时会不同于理想情况。主要是以下两种。
-
在mount流程中调用setState:不会进入update流程,队列在mount时合并修改并render。
-
在setTimeout/Promise回调中调用setState:将不会进行队列的批更新,而是直接触发一次update流程。 这是由于setState的两种更新机制导致的,只有在批量更新模式中,才会是“异步”的。
-
-
setState会引发一次组件的更新过程,从而引发页面的重新绘制。主要会涉及以下几个生命周期函数:
- shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)
- componentWillUpdate(被调用时this.state没有更新)
- render(被调用时this.state得到更新)
- componentDidUpdate
性能优化
减少diff算法触发
- 减少setState:主要在于非批更新阶段中(timeout/Promise回调),减少setState的触发次数。
- 父组件的render:会触发子组件进入update阶段(无论props是否更新)。此时最常用的优化方案即为shouldComponentUpdate方法。最常见的方式为进行this.props和this.state的浅比较来判断组件是否需要更新。或者直接使用PureComponent,原理一致。
合理diff
- 不使用跨层级移动节点的操作。
- 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点。
- 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。
PureComponent
资料参考:详解在React.js中使用PureComponent的重要性和使用方式
资料参考:React 的 PureComponent Vs Component
资料参考:何时使用Component还是PureComponent?
基本概念
PureComponent改变了生命周期方法 shouldComponentupdate,并且会自动检查组件是否需要重新渲染。这时,只有PureComponent检测到 state 或者 props 发生变化(引用类型的引用变化,或基本类型string number值变化)时,PureComponent才会调用 render 方法。
浅比较源码:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
使用pureComponent相当于在component中优化检测:
-----这是component中的优化 在purecomponent中默认包含-----
shouldComponentUpdate(nextProps, nextState) {
return (nextState.person !== this.state.person);
}
使用
class MyComponent extends PureComponent {...}
如果一个纯组件(PureComponent)的 state 或 props 引用了一个新对象,那么这个组件就会被重新渲染(re-render)。
handleClick() {
this.setState(prevState => ({
words: prevState.items.concat(['new-item']) ← 引用新对象 浅比较不同
}));
}
使用PureComponent的最佳情况就是展示组件,它既没有子组件,也没有依赖应用的全局状态。
- 父组件继承PureComponent,子组件继承Component时,如果渲染被父组件拦截,子组件也不会重新渲染。
- 父组件继承Component,子组件继承PureComponent时:父组件会重新渲染,子组件不一定会重新渲染。
如果prop和state每次都会变,那么PureComponent的效率还不如Component!(因为还要浅比较)
不要在PureComponent中使用shouldComponentUpdate,因为根本没有必要~
一些操作
事件监听
<button onClick = { (e) => this.deleteRow(id,e) } Delete </button>
等价于
<button onClick = { this.deleteRow.bind(this,id) } Delete </button>
具体方法 调用中 e/this → e
deleteRow (id, e){...}
判断执行
true && expression
为真才执行,取代if条件渲染
React-router
参考资料:react-router的实现原理
react-router就是控制不同的url渲染不同的组件。react-router在history库的基础上,实现了URL与UI的同步。
-
原理:DOM渲染完成之后,给window添加onhashchange事件监听页面hash的变化,并且在state属性中添加了route属性,代表当前页面的路由。
-
具体步骤:
1 当点击链接,页面hash改变时,触发绑定在 window 上的 onhashchange 事件;
2 在 onhashchange 事件中改变组件的 state中的route属性,react组件的state属性改变时,自动重新渲染页面;
3 页面随着 state 中的route属性改变,自动根据不同的hash给Child变量赋值不同的组件,进行渲染。
Redux
数据管理中心:单一数据源(所有state维护在一个根级store);状态只读(无法直接修改,严控修改执行);纯函数(只能用reducer描述修改)
Store:全局单例。
-
方法:
getState:获取state;
dispatch:触发action,更新state;
subcribe:订阅数据变更,注册监听器。
-
const store = createStroe(Reducer, initStore);
action:行为载体,映射响应Reducer。可成为数据载体,是store唯一数据源。
Reducer:修改行为的实质,用于描述如何修改数据的纯函数。
纯函数:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
原因:Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同(浅比较)。如果你在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state。两个state相同的话,页面就不会重新渲染了。因为比较两个Javascript对象所有的属性是否相同的的唯一方法是对它们进行深比较。但是深比较在真实的应用当中代价昂贵,因为通常js的对象都很大,同时需要比较的次数很多。因此一个有效的解决方法是作出一个规定:无论何时发生变化时,开发者都要创建一个新的对象,然后将新对象传递出去。同时,当没有任何变化发生时,开发者发送回旧的对象。也就是说,新的对象代表新的state。
处理异步:引入Redux-thunk或者redux-promise这种中间件,延迟事件的派发。
!!!!!!!!!!!!!写一个!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
React Hook
在函数定义组件中使用React特性
引入React到html中
layout.ftl 最初渲染页面
<html ...>
<body>
<div id = "app-container"></div>
<script type = "text/javascript" ... ></script
</body>
</html>
client.js 配置redux的页面
在此引入 store router
...
ReactDom.render(
...引入其他设置及组件
document.getElementById('app-container');
);