createPortal
createPortal 的出现为 弹窗、对话框 等脱离文档流的组件开发提供了便利,Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
const modalRoot = document.body;
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
render方法新增返回类型
render方法支持直接返回string,number,boolean,null,portal,以及fragments(带有key属性的数组),这可以在一定程度上减少页面的DOM层级
// 不需要再将元素作为子元素装载到根元素下面
render() {
return [
<div>一步 01</div>,
<div>一步 02</div>,
<div>一步 03</div>,
<div>一步 04</div>
];
}
新的组件生命周期钩子
static getDerivedStateFromProps(nextProps, prevState)
static静态方法,在es5中这么实现:
function Person() {}
Person.getCount = function () {}
以上就是static静态方法的原理。由于“this”只能获取属性是根据原型链,而静态方法不在原型链上,所以,在组件实例内无通过this调用static方法,static方法也无法根据"this"调用实例的其他方法。
static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// 当传入的type发生变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进行任何操作
return null;
}
// 你可能会用以下这样做,虽然这样做看起来也没问题,用上面的方法更加安全,不会对this做误操作
componentWillReceiveProps (nextProps) {
if (this.state.name !== nextProps.name) {
this.setState({
name: nextProps.name
});
}
}
componentDidCatch(error, info)
如果错误在组件的渲染或者生命周期方法中被抛出,则会触发该函数。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后在顶部或任何地方,你可以这样使用它
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
性能方面
lazy / Suspense
React.lazy() 提供了动态 import 组件的能力,实现代码分割。
Suspense 作用是在等待组件时 suspend(暂停)渲染,并显示加载标识。
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
React.Fragments
如果不喜欢写数组呢,可以使用这个(简写<></>),这样并不会在DOM中增加额外节点,相当于 render 返回数组元素。
return (
<React.Fragment>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
<div>一步 04</div>
</React.Fragment>
);
ref
React16中有两种创建Ref的方法
constructor () {
this.inputNameRef = React.createRef();
this.switchRef = React.createRef();
}
render () {
// 通过this.inputNameRef.current 可以获取到input实例
return (
<div>
<input ref={this.inputNameRef} />
<Switch ref={this.switchRef} />
</div>
)
}
render () {
// 通过回调ref的方式实现
// 通过this.inputNameRef 可以获取到input实例
// this.switchRef可以获取Switch的实例
return (
<div>
<input ref={(ref) => this.inputNameRef = ref} />
<Switch ref={(ref) => this.switchRef = ref} />
</div>
)
}
hooks
useState
// 传入初始值,作为 state
const [state, setState] = useState(initialState)
// `惰性初始 state`;传入函数,由函数计算出的值作为 state
// 此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props)
return initialState
})
useEffect
- Effect Hook 可以让你在函数组件中执行副作用操作,数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。
- React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,传递给 useEffect 的函数在每次渲染中都会有所不同,我们可以在 effect 中获取最新的 count 的值,而不用担心其过期
- 每个 effect 都可以返回一个清除函数, React 会在组件卸载的时候执行清除操作, useEffect 默认就会在调用一个新的 effect 之前对前一个 effect 进行清理。
- 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可,如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])
以下就是一个创建订阅的例子:
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
// 清除订阅
subscription.unsubscribe()
}
}, [依赖])
规则
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。
(*推荐启用 eslint-plugin-react-hooks 的 ESLint 插件)
useMemo和useCallback
useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);