React16.2-16.6
React16.2-16.6功能总结。涉及生命周期变化、StrictMode、Profiler、全新的context api和refs相关改动。
生命周期变化
-
移除componentWillMount、componentWillReceiveProps、componentWillUpdate (17.0弃用)
-
新增static getDerivedStateFromProps、getSnapshotBeforeUpdate
原因
在16使用的Fiber架构中, React组件渲染分为两个阶段。第一阶段是渲染阶段,这一阶段做的是Fiber的update,然后产出的是effect list(可以想象成将老的View更新到新的状态所需要做的DOM操作的列表)。这一个阶段是没有副作用的,因此这个过程可以被打断,然后恢复执行。第二阶段是提交阶段。渲染阶段产生的effect list只有在提交之后才会生效,也就是真正应用到DOM中。这一阶段往往不会执行太长时间,因此是同步的,这样也避免了组件内视图层结构和DOM不一致。这样就会导致render之前的生命周期在一次渲染中多次执行产生副作用。
(Fiber源码解读 | Fiber相关完全理解)) |
新生命周期图
getDerivedStateFromProps
- 组件
state.count
值为props.count * 100
static getDerivedStateFromProps(nextProps, prevState) {
return {
count: nextProps.count * 100,
};
}
-
getDerivedStateFromProps
return null表示不更新state。 -
getDerivedStateFromProps
拿不到实例的this
,所以如果比较this.props
和nextProps
只能将this.props
存在state
中。 -
props更新时重新请求
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.id !== prevState.prevId) {
return {
externalData: null,
prevId: nextProps.id,
};
}
// 不需要更新state
return null;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.externalData === null) {
// 请求
}
}
getSnapshotBeforeUpdate
- 返回值作为componentDidUpdate第三个参数
getSnapshotBeforeUpdate(prevProps, prevState) {
return 1;
}
- 结合componentDidUpdate使用例子
由于componentWillUpdate
和componentDidUpdate
执行的时间差,如果使用componentWillUpdate
,会导致从componentWillUpdate
中获取数据会不准确。
class ScrollingList extends React.Component {
listRef = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
// 是否在向列表中添加新条目?
// 捕捉滚动位置,以便我们调整滚动条。
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们拥有了快照值,则说明刚刚添加了新条目。
// 调整滚动条,以免使旧条目被新条目移出可视范围。
// (这里的snapshot 指的是从getSnapshotBeforeUpdate 返回的值)
if (snapshot !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}
StrictMode
-
StrictMode不会渲染真实UI
-
只在开发模式下运行
-
对其包裹的子组件进行检查(1.识别具有不安全生命周期的组件 2.旧式字符串ref用法的警告 3.检测多次执行渲染阶段的生命周期的副作用 )
Profiler
全新的context api
功能
共享那些被认为对于一个组件树而言是“全局”的数据,不必通过组件树的每个层级显式地传递 props 。
与老的context区别
-
老的 Context API, 在穿透组件的过程中,某个组件的 shouldComponentUpdate 返回了 false, 则 Context API 就不能穿透了。而新的 Context API 从 Provider 到其后代的 Consumers 传播不受 shouldComponentUpdate 方法的约束。
-
新的 Context API 性能提升了许多(老的api context同props一样一层一层往下传递,改变后需要重新渲染的节点远高于新的api)
基本用法
// 创建 context 实例
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
ThemedButton(props) {
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
Refs
新的refs创建方法
- React 会在组件加载时将 DOM 元素传入 this.myRef 的 current 属性,在卸载时则会改回 null。ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
转发refs
方法1: 为类组件添加 Ref,可以通过this.myRef.current.func 调用组件实例的方法来操作 dom。(不推荐此方法,1.你不能在函数式组件上使用 ref 属性,因为它们没有实例。 2.它会破坏组件的封装)
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建 ref 存储 textInput DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:通过 "current" 取得 DOM 节点
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
方法2:使用React.forwardRef
// 高阶组件
import React from 'react';
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('先前的属性:', prevProps);
console.log('当前属性:', this.props);
}
render() {
// 使用 forwardedRef 作为一个 ref 属性传入组件中
const { forwardedRef, ...rest } = this.props;
return (
<Component ref={forwardedRef} {...rest} />
);
}
}
// 使用React.forwardRef对LogProps组件进行转发
return React.forwardRef((props, ref) => (
{' 上面定义的LogProps组件接受一个forwarded属性 '}
<LogProps forwardedRef={ref} {...props} />
));
}
// FancyButton.js 子组件
import React from 'react';
import logProps from './logProps';
// 接受 props 和 ref 作为参数
// 返回一个React 组件
const FancyButton = React.forwardRef((props, ref) => (
<button class="fancybutton" ref={ref}>
{props.children}
</button>
));
// 使用高阶组件对其进行封装
export default logProps(FancyButton);
// 父组件
// app.js
class App extends React.Component {
constructor(props) {
super(props);
// 创建一个ref 名字随意
this.fancyButtonRef = React.createRef();
}
componentDidMount() {
console.log('ref', this.ref);
// this.ref.current 表示获取ref指向的DOM元素
this.ref.current.classList.add('primary'); // 给FancyButton中的button添加一个class
this.ref.current.focus(); // focus到button元素上
}
render() {
// 直接使用ref={this.fancyButtonRef}
return (
<FancyButton ref={this.fancyButtonRef}>子组件</FancyButton>
);
}
}
Comments