概念
特点
- 组件化
- Virtual DOM
流程
React,用户不会直接操作DOM,而是用户直接更改状态,让React帮助操作DOM。具体如下:
graph TB A(用户更改状态) --> B(Render) B --> C(新的完整的Virtual DOM) C1(旧Virtual DOM) --> D(DIFF比较差异) C --> D D--> E(一次性更新真实DOM) style A fill:greenyellow style E fill:greenyellow
注意点:
- 当父组件state变动,会引发所有子组件Render(即便子组件本身的state毫无改变)。如果不想引发所有子组件render,见步骤5。
- Render渲染出来的是完整的Virtual DOM。
- 利用DIFF算法,比较新旧两个Virtual DOM结构的差异(树对象的比较)
- 将差异变动,一次性渲染到真实DOM树。
如果不想引发所有子组件render,目前有两种方案:
shouldComponentUpdate() {return false}
immutablejs + PureComponent
期待
React增加了VDOM的比较时间、减少了浏览器操作。
在重复渲染的时才可能提高性能。
因此,React完成了框架的意义:降低开发者的开发成本,但真正的性能比不上经过认真优化过的原生JS。
为什么DOM操作浪费资源?
操作js对象
触发浏览器行为(罪魁祸首)
- 重绘paint——调用浏览器UI引擎渲染展示页面(耗CPU和内存)
- 重排layout——布局变动,造成重新计算(耗CPU,有时也很耗内存)
其中,重绘、重排 最浪费性能。
graph TB A(DOM操作 -- 触发 --浏览器行为) --> B>浏览器解析DOM生成DOM树] A --> C>浏览器解析CSS生成样式树] C--> D>进行layout和paint] B--> D D --> E(展现到设备)
为了减少重绘重排,可做如下努力:
- 用Canvas / CSS3 , 替代DOM动画
JQuery 减少DOM操作——
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 避免,频繁DOM操作
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i));
list.appendChild(item);
}
// 推荐,使用容器存放临时变更, 最后再一次性更新DOM
var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i));
fragment.appendChild(item);
}
list.appendChild(fragment);上述所讲Virtual DOM,也是减少DOM操作的一个手段: 通过预先比较,得到差异,一次性更新DOM。
react-router
传参
react-router 4.x 传参不再支持query string。query的值不会反映到url上,而是放在内存里,状态非持久,刷页面,query值丢失。
如果你想打开全新的浏览器窗口,必须将参数反馈到url地址,可采取如下办法:
办法1: 传递多个参数。
使用【 search + 字符串解析工具 】
1 | // 路由route定义 |
办法2: 传递少量参数
将变量放到路径
1 | // 路由route定义 |
高阶组件
graph TD A(共用组件) --引用--> B(组件1) A --引用--> C(组件2) A --引用--> D(组件3)
高阶组件定义–用作公共组件1
2
3
4
5
6
7
8
9
10
11
12
13
14import React from 'react';
import './box.less';
const Box = InnerComponent => class extends React.Component {
render() {
return (
<div className={`box-wrapper ${this.props.className}`}>
<InnerComponent {...this.props} />
<div className="box-mask" />
</div>
);
}
};
export default Box;
高阶组件调用–用共用组件包裹1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 组件1
import React from 'react';
import Box from '../Card/box';
class LineTrend extends React.Component {
}
export default Box(LineTrend);
// 组件2
import React from 'react';
import Box from '../Card/box';
class Pie extends React.Component {
}
export default Box(Pie);
// ...其他组件
hooks
react的特点是组件化,而定义组建的方式如下:
- 函数组件: function。数据驱动,只负责渲染。方便复用,可独立测试。
- 类组件: class。包含状态,生命周期。提供数据维护及渲染。不方便复用。
- hook: 让函数组件具备类组件的特性,比如状态和生命周期。
版本: React v16.8及以上
作用: use state and other React features without writing a class。利用function定义组件时,可使用state等React特性。(不一定要class才有状态和生命周期啦, function也可以!)
- 组件间状态逻辑的复用
旧有方式中,如果想复用状态,有如下方式:
- react:
render props
:维护状态的组件+负责渲染的子组件higher-order components
:高阶组件(一个函数接受一个组件作为参数)- 将state置于父组件,使其子组件可复用(陷入
wrapper hell
)
- redux: store + props 状态属于全局而非哪个组件,所以可以任意复用(状态不随组件而卸载,所以当使用该数据的组件都已被卸载,但数据仍然像幽灵一样存在)
hooks:允许复用状态逻辑(reuse stateful logic),而不更改组件层级。即,一个独立组件,既方便渲染又自己包含状态。
- 生命周期钩子函数
通常在componentDidMount中做很多不同业务类型的事情,比如ajax请求、事件监听等。有时还需在componentDidUpdate做一遍同样的事情。
hooks:希望把不同业务类型独立书写,逻辑更直观。
- class组件
类组件,存在this指向问题、不方便复用、最初敲定使用function后因业务变动需大动干戈改为class组件等问题。
hook:本质是带着状态的function,不存在上述问题。
注意点:React是通过Hook调用的次序来记录各个内部状态的,因此react规定我们必须把hooks写在函数的最外层,不能写在if-else等条件语句当中,来确保hooks的执行顺序一致。
useState
状态维护。替代state 和 onStateChange。
利用数组解构,在function中实现如下功能:
- 定义状态变量
- 定义更新状态方法(直接替换老状态、返回全新状态)
- 给出初始状态值
useEffect
状态更改后,做一些操作。替代生命周期函数(如,componentDidMount,componentDidUpdate和componentWillUnmount)。
- 获取数据
- 操作DOM
- 清理计时器
useEffect执行时机:
- react首次渲染和之后的每次渲染都会调用传给useEffect的函数。
- useEffect中函数的执行不会阻碍浏览器更新视图,是异步的。
- useEffect中执行清理操作,需要在其中返回一个清理函数。带return(从 effect 中返回一个 function,是 effect 可选的清理机制,每个 effect 都可以返回一个在它之后清理的 function, 当组件卸载时,React 会自己执行清理函数,进行清理工作。)
可以控制useEffect执行时机:
- 给出第二个参数,只有当这个参数改变时,才执行。
- 给出第二个参数为空数组[]时,只在首次渲染的时候执行(<=> componentDidMount,少用)。
useReducer
复杂状态维护。
参考:
react衍生
styled-components
用途
使样式组件化
案例
百度地图”自动搜索地址”功能。
该所得到结果下拉框的样式是”内部全局样式”。只能通过”覆盖原生样式类”实现自定义样式。无法将其挂在到某个DOM,或直接设置类名 / style等。
1 | // 引用百度地图api |
1 | /* 原生样式类 */ |
当该全局地图样式需要实现多套样式主题切换(换肤),该怎么实现呢?
以下方案无效:
- 切换主题时,动态切换类名。(因为无法给自动搜索地址注入类名)
- 切换主题时,动态切换style对象值。(因为无法给自动搜索地址注入style)
- 将不同样式写入不同css文件,切换主题时,动态引入css文件。
- 因为需要判断主题类型,因此不能采用import一开始就引入,只能采用require引入。
- require引入后,将不能卸载,多个主题文件同时存在,后者覆盖前者。
实现手段:
将主题样式组件化,动态引入/卸载组件。
1 | { |
1 | import { createGlobalStyle } from 'styled-components'; |