再读React
笔者最近空闲时间比较多,这也可能是笔者这未来人生中时间最自由的一个月。所以想抓紧时间巩固和学习一下新知识,在公司实习期间一直在写业务,所以自己学习的时间很少,为了追上同事的步伐,一定要抓紧这一个月又一个质的突破。
这篇文章就是记录我再读文档的时候一些知识盲区
关于生命周期
组件挂载
当组件实例被创建,并插入到DOM中,其生命周期调用顺序为
- constructor()
- 通过设置this.state来初始化State
- 为事件处理函数绑定实例
this.handleClick = this.handleClick.bind(this)
- static getDerivedStateFromProps()
- render()
- componentDidMount()
组件更新
- static getDerivedStateFromProps()
- shouldComponentUpdate():返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响(可使用
PureComponent) - render()
- getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM中捕获一些信息(例如,滚动位置)
- componentDidUpdate(prevProps, prevState, snapshot): 会在更新后立即调用,首次渲染不会调用。第三个参数是
getSnapshotBeforeUpdate的返回值
关于API
setState
1 | this.setState((state,props)=>{ |
forceUpdate
默认情况下当props或者states发生变化时,就会触发render()函数。当组件还依赖于其他变量时,可以使用forceUpdate()强制更新。
React.lazy
React.lazy函数能够让你像渲染常规组件一样处理动态引入的组件
用法如下
1 | const OtherComponent = React.lazy(() => import('./OtherComponent')); |
React.Suspense
Suspense直译为中文就是悬念的意思。可以用React.Suspense组件为组件添加loading状态,以作优雅降级。
1 | import {Suspense} from 'react'; |
==注意:Lazy、Suspense暂不支持服务端渲染。==
React.memo
适用于函数式组件而不适用于class组件。类似于PureComponent,通过记忆组件渲染结果的方式来提高组件的性能表现。
1 | const MyComponentMemo = React.memo(function MyComponents(props){ |
默认情况下只会对复杂对象进行浅层对比, 可自定义比较函数来控制对比过程。
1 | function MyComponent(props) { |
错误边界
错误边界是一种React组件,该组件可以捕获并打印发生在其子组件树任何位置的JavaScript错误,并且他会渲染出备用UI,而不是那些错误的子组件树。错误边界在渲染期间、生命周期方法和整个组件树中的方法捕获错误。但错误边界无法捕获下列情况
- 事件处理(
try,catch) - 异步函数(如
setTimeOut和requestAnimationFrame的回调函数) - 服务端渲染
- 错误边界组件自身抛出的错误
如何使用
如果一个class定义了getDerivedStateFromError()或componentDidCatch()这两个生命周期的一个或两个,它就变成了一个错误边界组件。
1 | class ErrorBoundary extends React.Component { |
Refs转发
将ref自动的通过组件传递到其子组件。
在下面的示例中,FancyButton 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button;
当ref挂载完成,将ref.current将指向Button的DOM;
1 | const FancyButton = React.forwradRef((props,ref)=>( |
在高阶组件中转发refs
定义一个高阶组件
LogProps,它的作用只是将组件的props输出到控制台;1
2
3
4
5
6
7
8
9
10
11
12
13
14function logProps(Component){
class LogProps extends React.Component {
componentDidUpdate(preProps){
console.log("preProps: "+preProps);
console.log("props: "+this.props);
}
render(){
return <Component {...this.props} />
}
}
return LogProps;
}定义一个组件
FancyButton。实际上导出的是LogProps这个高阶组件。1
2
3
4
5
6
7
8
9
10
11class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// 我们导出 LogProps,而不是 FancyButton。
// 虽然它也会渲染一个 FancyButton。
export default logProps(FancyButton);使用
FancyButton组件。实际上ref是挂载到LogProps上的1
2
3
4
5
6
7
8
9
10
11
12
13import FancyButton from './FancyButton';
const ref = React.createRef();
// 我们导入的 FancyButton 组件是高阶组件(HOC)LogProps。
// 尽管渲染结果将是一样的,
// 但我们的 ref 将指向 LogProps 而不是内部的 FancyButton 组件!
// 这意味着我们不能调用例如 ref.current.focus() 这样的方法
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;转发
refs。 修改一下logProps高阶组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function logProps(Component){
class LogProps extends React.Component {
componentDidUpdate(preProps){
console.log("preProps: "+preProps);
console.log("props: "+this.props);
}
render(){
const {forwradRefs, ...rest} = this.props;
return <Component refs={forwardRefs} {...rest} />
}
}
return React.forwardRefs((props,refs)=>(
// 给logProps组件传递一个forwardRefs的属性
return <LogProps forwradRefs={refs} {...props} />
));
}
注意: 高阶组件转发refs时,在React-DevTools调试中,可能会造成不便,可设置displayName来解决
深入JSX
JSX只是React.createElement(component,props,children)的语法糖。
- 自定义``
JSX类型不能是一个表达式。但JSX类型可以是一个由大写字母开头的变量。
Portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
用法
1 | // 把子元素渲染到domNode中。domNode是一个在任何位置的有效DOM节点。 |
Portals中的时间捕获与冒泡
使用portal,虽然会操作DOM树,但由于portal仍存在于React树,且与DOM树中的位置无关。所以portal中的事件,仍会冒泡到React树中的父组件。context等的效果也是跟普通的组件无异的。
在父组件里捕获一个来自 portal 冒泡上来的事件,使之能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。例如,如果你在渲染一个 <Modal /> 组件,无论其是否采用 portal 实现,父组件都能够捕获其事件。
create-react-class
除了class创建一个组件,和函数式组件,还可以通过create-react-class来新建一个组件。
1 | var createReactClass = require('create-react-class'); |