从React 16.0开始,React引入了以下新的特性(组件、API、生命周期函数、hook等)。从15.X升级到16.X是一种增强式的升级,之前的用法依然有效,基本可以直接将npm包进行平滑升级。但是随着React 16.5的发布,新特性基本已经完善和固定下来,为了享受新版本带来的更高效率架构和更规范的开发流程,现在是时候使用上React ^16.0 的新特性进行功能开发和代码重构。 我直接使用了React 16.5,具体变更请查看React 更新日志,以下是几个常用的新特性,每个特性都从与之前的比较和实例来说明:
一、生命周期函数
生命周期函数的改动有两个方面
- 新增:
static getDerivedStateFromProps(nextProps, prevState)
getSnapshotBeforeUpdate(prevProps, prevState)
componentDidCatch(error, info) - 不安全:
UNSAFE_componentWillMount(nextProps, nextState)
UNSAFE_componentWillReceiveProps(nextProps)
UNSAFE_componentWillUpdate(nextProps, nextState)
在16.X版本中依然可以使用不安全的周期函数(UNSAFE_前缀可加可不加),用法也和之前的一样。但是如果使用了新增的周期函数就不能同时使用不安全的周期函数,否则会报错并且不安全的周期函数不会被执行。
使用新增的周期函数的执行顺序还是按照阶段来说明:
- 装载阶段
constructor -> getDerivedStateFromProps -> render -> componentDidMount - 更新阶段
getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate - 卸载阶段
componenWillUnMount
- 新增的生命周期函数
- getDerivedStateFromProps周期函数的作用是根据props初始化或更新state,相当于绑定了props。有两个参数分别是nextProps(本次渲染的props)和prevState(当前组件的state),通过对比nextProps和prevState对应字段的值是否相同从而获知外界传入的props是否改变,然后返回更改后的state。
注意:关于更新阶段在16.4后的getDerivedStateFromProps作用于所有更新方式(自身state变化,传入props变化,自身forceupdate, 父组件引发的re-render) 这意味着我们的组件里state接收了从外部传递的props初始化后就不能通过this.setState方法修改自己,例如下面例子会导致state不能内部更新与 15.x对比:
static getDerivedStateFromProps(nextProps, preState) {
return {
info: nextProps.info, //state.info绑定了props.info
text: preState.text // state.text由组件自己管理
}
}
handleChange = () => {
this.setState({
info: 'change', //组件内setState无法更新state.info
text: 'new text' // 可以正常更新
})
}
React 15.X
class Parent extends Component{ //父组件
render(){
return (
<Child age={18} />
)
}
}
class Child extends Components{ //子组件
constructor(props){
super(props);
this.state = {
age: this.props.age //装载阶段初始化state
}
}
componentWillReceiveProps(nextProps){
if(nextProps.age !== this.state.age){
this.setState({
age: nextProps.age
}) // props.age改变时修改state.age
}
}
handleChange = () => {
this.setState({
age: 20 // 可以正常更新
})
}
render(){...}
}
刚开始使用getDerivedStateFromProps可能会觉得反而比之前的使用场景少了,但其实我们将props映射到state的场景是很少的,多数情况下界面可以直接使用props的值,只有当需要对props进行计算或处理再使用的情况下才需要放进state来使用,而这时的state也无需通过组件内部去更改。
- getSnapshotBeforeUpdate作用是替代componentWillUpdate,有两个参数分别是prevProps和prevState。它放在了render函数后、componentDidUpdate之前执行,此时的prevProps和prevState已经是更新后的props和state,但dom结构还没渲染,因此可以获取到dom节点的信息返回,componentDidUpdate中可以拿到它的返回值,然后获取最新dom节点信息与之进行对比,从而实现某些功能。
列表项数目改变时scroll到最后一条
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length !== this.props.list.length) {
const list = this.listRef.current //dom渲染前的列表元素ref
return list.scrollHeight - list.scrollTop
}
return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
// snapshot 是getSnapshotBeforeUpdate返回的偏移量
if (snapshot !== null) {
const list = this.listRef.current //dom渲染后的列表元素ref
list.scrollTop = list.scrollHeight - snapshot // 滚动到之前的位置
}
}
- componentDidCatch React组件发生错误时的处理逻辑,调用第三方的错误监听服务。用法是在上级组件中定义componentDidCatch,他有error和info 两个参数,可以在函数中处理错误.
class PotentialError extends React.Component { //定义错误处理组件
constructor(props) {
super(props);
this.state = { error: false };
}
componentDidCatch(error, info) {
this.setState({ error, info });
}
render() {
if (this.state.error) {
return <h1>Error: {this.state.error.toString()}</h1>;
}
return this.props.children;
}
}
<PotentialError><App /></PotentialError> // 包裹应用
二、Context
使用Context 主要是解决props向多层嵌套的子组件传递的问题,原理是定义了一个全局对象。新的Context的使用方法跟旧的有很大差别
// React 15.x需要自己开发定义提供 Context的父组件,然后用这个父组件包裹嵌套多层的子组件。
class Provider extends Component {
getChildContext(){
return {
age: 18
}
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
age: PropTypes.number
};
// 子组件
class Child extends Component {
constructor(props,context) {
super(props,context); //super 带上context 参数
}
render(){
const {age} = this.context; //使用this.context访问
...
}
}
Child.contextTypes = {
age: PropTypes.number
};
// React 16.5 使用 React.createContext()
const MyContext = React.createContext();
// 使用 MyContext.Provider 包裹子组件
<MyContext.Provider value={{age: 18}}>
<Child />
</MyContext.Provider>
//子组件使用 MyContext.Consumer获得 Context
class Child extends Component {
render(){
return (
<MyContext.Consumer>
{(context) => {
const {age} = context;
return ...
}}
</MyContext.Consumer>
)
}
}
三、Portal 和 Refs
Portal 是React 16 的全新特性,可以将组件渲染到非根节点其他 DOM 节点上,常用的场景是实现弹框组件的改造
用法是使用ReactDOM.createPortal(component, domNode) component是要渲染的组件,domNode是挂载的dom节点。
React 16 使用React.createRef 创建Ref对象,当组件通过ref属性绑定这个对象后就能通过this.myRef.current来获取dom节点或react组件实例,也可配合React.forwardRef将子组件的ref暴露给父组件,以下是使用的区别:
// React 15.X
class Parent extends Component {
getChildRef= (ref) => {
this.myRef = ref;
}
render() {
<Child childRef={this.getChildRef} />
}
}
class Child extends Component {
constructor(props) {
super(props)
}
render() {
return <div ref={this.props.childRef} />
}
}
// React 16.5
class Parent extends Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <Child ref={this.myRef} />
}
}
const Child = React.forwardRef((props, ref) => <div ref={ref} />);
四、React Hooks
react hook 是 react 16.8后新增的功能,它能够让函数组件管理自己的状态和模拟出生命周期,并提供几个组件优化的方法。
在之前函数组件没有生命周期和状态,只能由父组件更新或外界传入props更新触发重新渲染,由于完全依赖props因此作用局限于实现功能单一的无状态的展示组件。有了hook之后完全可以替代传统class组件的写法,提高代码可读性和质量。不足就是之前已经养成了面向对象的开发思想需要改变,但这是不强制的,react没有废除class组件的计划。
- useState:
const [num, setNum] = useState(0);
setNum(1);
初始值只绑定一次,运行setXX方法后会重新执行、渲染。
- useEffect:
useEffect可执行副作用操作,模拟了生命周期
useEffect(() => {
}) // 加载和每次更新完都执行,相当于componentDidUpdate, componentDidMount
useEffect(() => {
},[]) // 加载阶段执行 ,相当于componentDidMount
useEffect(() => {
},[datas]) // 加载执行一次, 更新时当datas改变才执行
useEffect(() => {
return () => {
}
},[]) -> 卸载时执行return的函数
- useRef:
获取组件实例或dom元素
function Home() {
const inputEl = useRef(null);
inputEl.current.focus();
return (
<>
<input ref={inputEl} type="text" />
</>
);
}
useMemo:
缓存计算结果或对象
const data = useMemo(()=>{
return {name}
},[name]) // 当name改变时data才改变
useCallback:
缓存函数,避免每次更新都重新定义函数
const onChange = useCallback((e)=>{
setText(e.target.value())
},[]) // 有依赖时,将依赖项填入数组中
以上就是React 16 新特性的简单介绍,实际上前端开发中经常遇到新技术出现或者技术升级的情况,我们应该紧跟技术进步的步伐,从文档入手,再结合开源项目和最佳实践进行练手,相信很快就能掌握它们。