time: 2018.10.16
update: 2018.11.15
heyunjiang
这里总结的是 react 的基本使用,包括官网所有 api 及使用技巧。也作为以后 react 使用的参考,每当官网有更新,都会更新在这里
目录
1 为什么总结这篇文档
2 熟练使用 react
2.1 基本功能
2.2 错误边界
2.3 高阶组件
2.4 setState
2.5 示例代码
2.6 虚拟 dom
3 问题归纳
4 问题解答
4.1 为什么使用 react ?它与 vue 相比较的优劣势是什么?
react: v16.5.2
time: 2018.10.18
update: 2018.11.15
包含 react 思想、设计理念、运行原理、格式要求
React.createElement()
包装起来,如 代码1
xss
攻击虚拟 dom 层
中。区别于浏览器 dom 元素,这里的元素是 react 实现的一套 dom 系统,属性也是参考浏览器实现,有许多差别真实 dom 层
操作,采用 ReactDOM.render(element, document.getElementById(‘root’)) 方法渲染defaultValue
和 defaultChecked
2个默认值属性const {Provider, Consumer} = React.createContext(defaultValue);
,使用 Consumer 订阅 Provider 的值。使用 context
,其更新方式不受 shouldComponentUpdate() 生命周期方法影响,只要 Provider 值变,其下面的 Consumer 就会更新。<></>
补充
value={this.state.value}
来指定表单项值,并且通过事件调用 setState 更新值包含 react 静态方法、组件生命周期方法、组件类型及属性
React.PureComponent
:与 Component 的却别是更新判断 props 的深度差别。在组件更新时,执行 props 浅比较判断更新。<></>
setState(updater, [callback])
:见2.4 setStatecomponent.forceUpdate()
:强制调用 render() ,并忽略 shouldComponentUpdate()生命周期方法:
super(props)
在构造函数中写其他表达式之前需要先被调用。在组件存活期间只会被调用 1次static getDerivedStateFromProps()
:组件静态方法,当组件实例化后和接受新 props 时会被调用,返回 state 用以更新组件 state 值。用于替换原生命周期方法 componentWillRecevePorps 。在组件存活期间只会被调用 多次getSnapshotBeforeUpdate()
:在更新前拦截旧数据,它的返回值作为 componentDidUpdate() 的参数传入。在组件存活期间只会被调用 多次componentDidCatch()
: 在组价发生错误时调用。在组件存活期间只会被调用 1次逐渐被废弃的生命周期方法
ReactDOM 实现了操作真实 dom 接口,兼容主流浏览器及 ie9+
这里归纳一下 react 实现的 dom 元素与浏览器自带的 dom 元素的差别
defaultChecked
是非受控属性,设置元素初次加载时的状态dangerouslySetInnerHTML
属性接口,值必须为一个函数,函数返回包含 __html
属性的对象,浏览器为 dom 对象设置 property innerHTML
htmlfor
替代浏览器节点上的 for 属性使用 react-dom/test-utils
搭配 Jest
一起做 react 组件测试
tips
time: 2018.10.23
update: 2018.10.23
设计目标:保护整个应用,捕获部分 UI 异常
功能描述:用于捕获其子组件树 javascript 异常,记录错误并展示一个回退的 UI
错误捕获位置:
无法捕获的错误:
实现方式:componentDidCatch(error, info)
使用目的:解决因部分组件问题,而造成整个应用 dom 树卸载,应用出现白屏问题。使用错误边界,可以保证整个项目正常运行
错误组件定位:babel-plugin-transform-react-jsx-source
高阶组件可以返回有状态组件与无状态组件
这里列出高阶组件应遵守的一些约定
WrappedComponent.displayName || WrappedComponent.name
访问到注意事项
hoist-non-react-statics
ref
属性:ref 属性不想其他 props 一样可以通过传递进原组件,ref 不是一个 prop;react 对 ref 属性有一层处理,会创建使用 ref 属性所在组件的一个 dom 指向。解决方案:使用 React.forwardRef()
API标准写法:
setState((prevState, props) => stateChange, [callback])
// 或者
setState(stateChange, [callback])
问:componentWillUpdate() 在什么时机触发呢?
猜想:在调用 setState 触发或 props 变化的时候。
jsx 编译结果: createElement( tag, attrs, child1, child2, child3 );
// 代码1 jsx 编译
// jsx
const element = <h1>Hello, world!</h1>;
const ele = (<div>xixi<element /></div>);
const ele1 = (<div>haha<element /><ele /></div>);
ReactDOM.render(ele, document.getElementById('root'));
// 编译后的 jsx
const element = React.createElement('h1', null, 'Hello, world!');
const ele = React.createElement('div', null, 'xixi', React.createElement('element', null));
const ele1 = React.createElement('div', null, 'haha', React.createElement('element', null), React.createElement('ele', null));
ReactDOM.render(ele, document.getElementById('root'));
// React.createElement('h1', null, 'Hello, world!') 返回结果
const element = {
type: 'h1',
props: {
children: 'Hello, world'
}
};
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 用法
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
// render prop 技术
class Child extends React.Component {
render() {
return <components value={this.props.value} />
}
}
class Parent extends React.Component {
render() {
return <div>{this.props.render(this.state)}</div>
}
}
function withParent (Component) {
class withParentComponent extends React.Component {
render() {
return <Parent render={(value)=><Child value={value} />} />
}
}
withParentComponent.displayName = 'withParentComponent';
return withParentComponent;
}
// hoc使用错误示例
function logProps(InputComponent) {
InputComponent.prototype.componentWillReceiveProps(nextProps) {
console.log(this.props, nextProps);
}
return InputComponent;
}
const EnhancedComponent = logProps(InputComponent);
// hoc使用正确示例
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log(this.props, nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
}
//hoc使用传递 ref 解决方案
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
return <Component ref={forwardedRef} {...rest} />;
}
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}
这一节单独拿出去总结,因为属于 react 最核心的部分,不在基础掌握范围中
finished
finished
finished
finished
finished
time: 2018.10.16
update: 2018.10.29
isFinish: false
本科毕业设计做的项目用的是 react ,对 react 相对更熟悉,react 生态圈也更好,各种模块很完善。
现在使用了一段时间 react ,使用的技术栈为: webpack4 + react 16 + redux + react-router4 + redux-saga
,除了 webpack4、 react 源码外,redux + router 系列的源码也都看过了,知道了技术栈中各个模块在系统构建过程中充当什么角色,承担了什么责任。
先熟悉一下 react 实现的基本功能,也就是 第二点 再来看这个问题
dom 操作是页面性能的瓶颈
react 相较于 vue ,其学习曲线更加陡峭,为什么要选择它呢?
time: 2018.10.18
update: 2018.10.22
isFinish: true
在 react 官网文档-高级指引-
Reconliliation
一章中,解释了这个过程
当组件内部状态变化,react 只会更新必要的部分,即子组件变化不会影响到父组件的变化;
当父组件变化,子组件没有变化时,是不是所有子组件都会更新?
之前看文档,有谈到 react 是
粗粒度
更新,即父组件变化,子组件全部更新;vue 是细粒度
更新,只有父组件变化,则只更新父组件。(这里的更新是指重新创建 dom 树,在浏览器上重新绘制一遍)
答:这种观点是错误的。具体看下面的对比算法,react 是否是粗粒度更新,试更新情况而定
如果想要子组件不被父组件的更新而更新,可以设置 shouldComponentUpdate()
生命周期函数返回值。如果父组件的 shouldComponentUpdate 返回 false ,那么父组件及其所有子组件不会被更新。
官网更新算法
时间复杂度:O(n)
基于2点假设:
对比算法:
不同类型的元素
:每当根元素有不同类型,不管是 dom 元素还是组件元素,react 将卸载旧树并重新构建新树。会调用 componentWillUnmount()
, componentWillMount()
和 componentDidMount()
相同类型的 dom 元素
:变化前后如果元素为 dom 类型,则比较该 dom 的属性,只更新该 dom 变化的属性,然后递归更新其子元素相同类型的组件元素
:当更新前后组件类型相同时,实例任然保持一致,保留实例的状态。 react 通过 props 来产生新元素,依次调用 componentWillReceiveProps()
和 componentWillUpdate()
方法,最后调用 render()
方法,然后递归更新其子元素递归子节点
:当因父组件变化,需要更新时,会遍历子节点。如果子节点非循环遍历生成的,react 循环每个子节点,根据对比算法的1,2,3点进行判断对比更新;如果子节点属于循环遍历生成的,则每个子节点都添加一个 key 属性,通过匹配 key 来寻找原有的子节点更新,或者新增子节点。time: 2018.10.18
isFinish: true
加个小括号,是解决 分号自动插入
的 bug
time: 2018.10.24
isFinish: false
为什么要这么做:在将要更新的 state 数据放入队列中,react 不会立即更新 state ,为了保证组件性能,react 会挑选合适的时候更新 state 。
更新时机:在每次事件循环中,只执行一次 setState
function defer( fn ) {
return Promise.resolve().then( fn );
}
time: 2018.10.18
isFinish: true
表面说的是帮助 react 识别哪些元素发生了改变,key作为元素的唯一标识;除了列表,凡是通过循环生成后代的,都应该添加 key 属性。
非循环生成的 react 元素,react 通过遍历,对比前后 react 树的节点,通过对比算法1,2,3点进行更新。
而循环生成的 react 元素,react 通过其 key 值,定位前后节点变化情况,通过对比算法1,2,3点进行更新。
问:非循环生成的 react 元素,是怎么定位的呢?
解释:比如使用{isShow?<Show>:null}
或{isShow&&<Show>}
方式,当 isShow 值变化的时候,react 如何定位该节点后面的兄弟节点,判断是原有节点还是新增的呢?
答:
time: 2018.10.18
isFinish: true
使用了 jsx ,不管使用的是 react 组件,还是 react 元素,最终都会被编译成 React.createElement()
,所以必须引入 react 包
time: 2018.10.18
isFinish: false
猜想:在编译过程中,jsx 会被转换成一个 createElement()
对象,标签名会变成第一个参数,如果不是组件,则会变成字符串,组件则变成组件名称,也就是一个变量。为什么不能写成 components[props.storyType]
这种表达式呢? 首先,首字母必须大写;其次,编译过程是不会做 js 代码执行,去计算结果的。
time: 2018.10.18
isFinish: true
都为 react 组件,区别就在于实现更新上,shouldComponentUpdate
上,做 props 和 state 数据比较。React.Component 会对传入的 props 和 state 做深层数据比较,判断是否更新组件,而 React.PureComponent 只会对数据做浅比较,只要数据第一层没有变化就不更新,所以它的速度会快许多。
同时,在继承 React.PureComponent 的组件中,其子组件不要有复杂的数据结构
time: 2018.11.15
isFinish: false
这2者都会在组件更新前,接收到新 props 时触发,此时用于设置 state 来更新 state ,然后才更新组件。
但是 getDerivedStateFromProps 还会在组件初始化时被调用,并且使个 static 静态方法。
是不是 react 就是使用 getDerivedStateFromProps 来替代 componentWillReciveProps 呢?它出现的价值在哪里?
time: 2018.11.15
isFinish: false
setState 会更新组件的数据,是在对应的组件上执行的这个命令,知道更新的是这个组件的数据,知道把这个组件作为入口,执行 diff 更新算法,遍历子组件。
但是,如果是 redux 做了数据管理呢?把所有的数据都拿出来了,每次 dispatch 更新数据时,不能对应到某个组件,那么它又是怎么对应到 virtual dom 的呢,它怎么知道把哪个节点作为入口呢?
在 vue 中,每次 set 数据的值 this.hello = 'world'
时,会触发 vue 的 watch 方法,那它又是怎么对应到某个组件的呢?。
问题总结:在数据更新的时候, react 和 vue 是怎么去更新 dom 的呢?
hooks 是 16.8 版本新增的特性,用以解决下列问题
状态复用
问题:同 vue2 mixins,react 之前也是提供的 providers、consumers、高阶组件、render props 抽象层实现状态复用,但是存在多层抽象之后不太容易找到数据来源管理维护
问题:大家都在往生命周期方法中加入逻辑处理,代码比较长,不好维护hooks 注意事项
hooks 类型
包括状态复用和内部逻辑复用,使用自定义 hooks,约定如下
use
开头