1.React 16版本以前渲染一个组件最外层有时要使用到一个无意义的<div>
元素作为包裹元素,否则会报错;React 16.2版本以后,新增了react.fragment
API,不需要外层有包裹元素时,可以使用<></>
或者<Fragment></Fragment>
(需要导入import React, { Fragment } from 'react'
)
jsx
- 注释
// 注释一,推荐 {/* xxx */} // 注释二 { // xxx }
- 语法
1. label中的for -> htmlFor 2. 类名class -> className
react vscode 插件
- simple react snippets代码补全插件
react 子组件对父组件的传值进行类型校验
PropTypes
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Test extends Component {
}
Test.propTypes = {
index: PropTypes.number,
addr: PropTypes.string,
addNum: PropTypes.func,
}
export default Test;
componentDidUpdate(preProps, preState, snapshot)
componentDidUpdate(preProps, preState, snapshot)
在更新后会立即调用,首次不会调用;第三个参数在组建中使用了getSnapshotBeforeUpdate()
,则snapshot
值为该函数的返回值,否则为undefined
;- demo:
import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; const LazyComponent = React.lazy(() => import('./components/Lazy')); class App extends React.Component { constructor(props) { super(props); this.state = { greet: 'hello, React 16' } this.changeState = this.changeState.bind(this); } getSnapshotBeforeUpdate(prevProps, prevState) { return 1; } componentDidUpdate(preProps, preState, snapshot) { console.log('preProps: ', preProps); // preProps: {} console.log('preState: ', preState); // preState: {greet: "hello, React 16"} console.log('snapshot: ', snapshot); // 1 } changeState() { this.setState({ greet: 'Changed' }) } render() { return ( <Suspense fallback={<div>loading...</div>}> <LazyComponent greet={this.state.greet} /> <button onClick={this.changeState}>change</button> </Suspense> ) } } ReactDOM.render(<App/>, document.getElementById('root'))
React生命周期
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.input !== this.props.input) {
return true;
} else {
return false;
}
}
- 在React16版本中,以下方法被标记为不安全方法,应该避免使用:
UNSAFE_componentWillMount()
UNSAFE_componentWillUpdate()
UNSAFE_componentWillReceiveProps()
- React16版本新增的生命周期函数:
static getDerivedStateFromProps()
—— 仅用于组件在props变化时更新state;getSnapshotBeforeUpdate()
static getDerivedStateFromError()
componentDidCatch()
- 挂载时生命周期函数调用顺序:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
- 更新时生命周期函数调用顺序:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapShotBeforeUpdate()
componentDidUpdate()
- 卸载时
componentWillUnmount()
- 错误处理
static getDerivedStateFromError()
componentDidCatch()
constructor()
使用:
不初始化state或者进行方法绑定时,不需要为组件实现构造函数;(不调用时默认会被加一个空的constructor
)super
使用
构造函数中的操作不应该具有副作用;
super
仅能用于以下两种方式:- 函数调用
super()
,通常用于构造函数中; - 访问父类原型链属性或方法:
super.prop
或super[prop]
- 函数调用
class Parent {
constructor() {
this.name = 'fn';
}
getName() {
return this.name;
}
}
Parent.prototype.className = 'Parent';
class Child extends Parent {
constructor(props) {
super(props);
}
getName() {
return super.getName() + ' from child.'
}
getParent() {
return super.className;
}
}
let child = new Child();
console.log(child.getName()); // 'fn from child.'
console.log(child.getParent()); // 'Parent'
React.lazy与Suspense
React.lazy()
提供了动态加载组件的能力;Suspense
是react内置的组件,用于懒加载组件时显示一些站位信息或提示信息(功能类似于loading过渡文字或动画);
// Lazy.js
import React from 'react';
export default props => (
<div>{ props.greet }</div>
)
// index.js
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
const LazyComponent = React.lazy(() => import('./components/Lazy'));
const App = () => {
return (
<Suspense fallback={<div>loading...</div>}>
<LazyComponent greet="hello, react16" />
</Suspense>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
Refs转发
refs
转发允许某些组件接收ref
,并允许其向下传递ref
;根据官方文档,主要应用包括:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
- 在 DevTools 中显示自定义名称
/**
* 转发 refs 到 DOM 组件
**/
// RefTrans.js
import React from 'react';
export default React.forwardRef((props, ref) => (
<button className="fancy-button" ref={ref}>
{props.children}
</button>
))
// index.js
import React from 'react';
import FancyBtn from './components/RefTrans';
class App extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
componentDidMount() {
console.log('ref: ', this.ref.current)
}
render() {
return (
<FancyBtn ref={this.ref}>Ref转发</FancyBtn>
)
}
}
HOC
- 定义:HOC(High-Order Component)即高阶组件,实质是一个函数,参数和返回值均为组件;个人感觉HOC在某些方面和函数柯里化有点像,某种程度上可以用于组件定制化。
- 常见的高阶组件:
react-redux
中的connect
是一个高阶函数,内部返回一个高阶组件; - 用途:属性代理、反响继承、渲染劫持等
- demo:
class Btn extends React.Component { render() { return <button style={{color: this.props.color}}>{this.props.desc}</button> } } const customizedBtn = (WrappedComponent, config) => class CustomizedBtn extends React.Component { render() { let props = { ...this.props, color: config.color } return <WrappedComponent {...props} /> } } // 仿react-redux中的connect函数 const customizedBtn2 = (config) => (WrappedComponent) => class CustomizedBtn extends React.Component { render() { let props = { ...this.props, color: config.color } return <WrappedComponent {...props} /> } } const RedPrimaryBtn = customizedBtn(Btn, {color: 'red'}); const BlackSheepBtn = customizedBtn2({color: 'blue'})(Btn)
Portals
- 意义:
Portals
允许你将一个子组件渲染到父组件之外的DOM节点上;比如一些弹窗等; - 用法:
ReactDOM.createPortal(child, container)
——child
为需要渲染到外部的子元素(包括react元素、数组或fragments、字符串或数值、布尔类型或null);container
为DOM元素; - demo:
// MyPortal.js import React from 'react'; import ReactDOM from 'react-dom'; export default class extends React.Component { constructor(props) { super(props); this.el = document.createElement('div'); } componentDidMount() { const portalRoot = document.getElementById('portal') portalRoot.appendChild(this.el); } render() { return ReactDOM.createPortal( this.props.children, this.el ) } } // index.js render() { <> <div id="portal"></div> <MyPortal> <span>myportal</span> </MyPortal> </> }
easy mock网站模拟接口数据,在无后端支持时独立开发,无缝衔接
react-transition-group动画库
包含三个模块:Transition、CSSTransition、TransitionGroup
react hooks
从React 16.8版本开始,新增的React hooks可以让用户在不用创建react class的同时可以使用state
等react属性;
React框架分层
-
Virtual DOM层:构建虚拟DOM树,描述页面长什么样
-
Reconciler层:负责调用组件生命周期方法,计算DOM diff等;
Reconciliation:
The algorithm React uses to diff one tree with another to determine which parts need to be changed翻译:Reconciliation是React一种用于计算两个dom tree之间差异并决定哪些部分需要更新的算法
Reconciliation的核心是:- 不同类型的组件会生成不同的DOM Tree,React不会尝试对它们做diff,而是使用新的Dom Tree替换旧的;
- 列表的diff依赖于key值,key的取值应该是稳定、唯一并可预测的;
-
Renderer层:负责根据虚拟DOM树和diff patch渲染对应页面;
React Fiber
React Fiber是React 16提出的新概念,是React重构的核心,主要是对Reconciler层的重写(Stack Reconciler ⇒ Fiber Reconciler
);
Fiber Reconciler
包括两个阶段:render phase
和commit phase
-
render phase
可以异步执行,调用的生命周期函数列表:[UNSAFE_]componentWillMount (deprecated) [UNSAFE_]componentWillReceiveProps (deprecated) getDerivedStateFromProps shouldComponentUpdate [UNSAFE_]componentWillUpdate (deprecated) render
-
commit phase
通常是同步执行(因为该步骤涉及到真实DOM的更新及UI展现),调用的生命周期函数:getSnapshotBeforeUpdate componentDidMount componentDidUpdate compoentWillUnmount
React 15及以前的版本,在渲染大型组件时,由于其Reconciler层和Renderer层的执行是连续不可间断的;如果遇到大型组件;组件的执行时间有时会非常长,可能会阻塞动画渲染、用户交互等高实时性的任务,出现掉帧等,影响了用户体验;
我们知道,页面的显示其实就是通过一系列数据渲染得到视图,实质就是页面渲染相关的函数调用;
UI = render(data)
React 15及以前的版本,Reconciler的实现是基于内置的调用栈的同步递归模型去遍历Dom Tree;每次更新需要栈中函数全部调用结束,即栈空为止;这个过程是不被打断的,一次可能执行很长时间,阻塞页面渲染,所以会导致掉帧等;
由于调用栈进行视图更新的过程是连续的,为了把React渲染任务切分为增量单元分段执行,React Fiber针对React组件对调用栈进行了部分重写;使其变为一个链表结构;
特点
React Fiber引入的目的是为了增强动画、布局等实时性要求高的任务的稳定性;
- 主要特点是增量渲染(incremental rendering)—— 即可以把渲染工作进行切片,并在多帧内的空闲时间完成渲染工作;
- 为不同的更新任务分配不同的优先级,根据优先级的高低优先执行;
- 任务的暂停与恢复;
- 重用已完成的任务;
- 终止不再需要的任务;
- 当新的任务进来时,旧的任务可以被暂停、终止或重用;如果新任务优先级更高,则挂起旧任务并优先执行新任务;
Fiber node structure
React Fiber的每个节点都是一个对象fiber,其数据结构表示为:
class Node {
constructor(instance) {
this.instance = instance;
this.child = null;
this.sibling = null;
this.return = null;
}
}
区别于React Fiber
,fiber是一个组件输入/输出信息的对象,可以看作一个虚拟的stack frame
;每个React元素都有一个对应的fiber
,它包含以下属性:
-
type
和key
:用途与组件类似,Conceptually, the type is the function (as in v = f(d)) whose execution is being tracked by the stack frame.
Along with the type, the key is used during reconciliation to determine whether the fiber can be reused.
翻译: 通常,type是一个函数,该函数的执行用于追踪栈帧;key通常用于在reconciliation
阶段判断该fiber
是否可以重用; -
child
和sibling
:用于指向其他fibers
;child
通常是组件render方法的返回值;sibling
是render方法返回的顶层元素(数组形式,此时child为数组首元素;Fiber支持render函数中返回多个元素)function Parent() { return [<Child1 />, <Child2 />] } // child是Child1, siblings是Child1和Child2
-
return
:reference to the parent -
output
:每一个fiber最终都有output,但是output只在一些Host Components
(DOM节点)中产生;这些输出最终给Renderer用于应用更新;
举例说明,定义一个组件ClickCounter
:
class ClickCounter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => {
return {count: state.count + 1};
});
}
render() {
return [
<button key="1" onClick={this.handleClick}>Update counter</button>,
<span key="2">{this.state.count}</span>
]
}
}
这里,ClickCounter
组件对应的fiber node的数据结构:
{
stateNode: new ClickCounter,
type: ClickCounter,
alternate: null,
key: null,
updateQueue: null,
memoizedState: {count: 0},
pendingProps: {},
memoizedProps: {},
tag: 1,
effectTag: 0,
nextEffect: null
}
span
DOM节点
{
stateNode: new HTMLSpanElement,
type: "span",
alternate: null,
key: "2",
updateQueue: null,
memoizedState: null,
pendingProps: {children: 0},
memoizedProps: {children: 0},
tag: 5,
effectTag: 0,
nextEffect: null
}
React Fiber基于新的浏览器API—— requestIdleCallback
和requestAnimationFrame
进行实现;
requestIdleCallback
调度一个低优先级的函数在浏览器空闲时间执行;
requestAnimationFrame
调度一个高优先级的函数在下一个动画帧中执行;