博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React@16.3 全新的Context API进阶教程
阅读量:7104 次
发布时间:2019-06-28

本文共 5560 字,大约阅读时间需要 18 分钟。

前言

最近看了下React16.3的新文档,发现官方悄悄地改了很多东西了。其中我最感兴趣的自然就是这个全新的Context API了。所以写了这篇文章来总结分享一下。其他的变动在这篇文章里或许会提及。

本文你可以在我的上面找到,转载请标注这个地址就行了。

什么是Context API

Context API是React提供的一种跨节点数据访问的方式。众所周知,React是单向数据流的,Vue里面的props也借鉴了这一思想。

但是很多时候,这种单向数据流的设定却变得不是那么友好。我们往往需要从更高层的节点获取一些数据,如果使用传统的prop传递数据,就需要每一层都手动地向下传递。对于层次很高的组件,这种方法十分地烦人,极大地降低了工作效率。

于是,React使用了Context APIContext API存在已久,但是旧的Context API存在很多问题,并且使用起来也并不是特别方便,官方并不建议使用老版本的Context API。于是很多开发者选择了Redux之类的状态管理工具。

受到Redux的影响,React在16.3.0版本中推出了全新的Context API

一些你需要提前知道的东西

  1. 众所周知,长期起来JavaScript一直没有模块系统。nodejs使用require作为弥补方法。ECMAScript6之后,引入了全新的import语法标准。import语法标准有个尤为重要的不同(相比较require),那就是:import导入的数据是引用的。这意味着多个文件导入同一个数据,并不是导入的拷贝,而是导入的引用。

  2. react@16.3的声明文件(d.ts)貌似没有更新,意味着如果你现在使用Typescript,那么可能会报错。

  3. React现在推荐使用render propsrender props为组件渲染的代码复用以及代码传递提供了新的思路,其实本质上就是通过props传递HOC函数来控制组件的渲染。

  4. 或许你曾经听过“Context API是用来替代Redux”之类的传闻,然而事实并非如此。ReduxContext API解决的问题并不一样,会造成那样的错觉可能是因为他们的使用方法有点儿一样。

  5. React16.3有几个新特性,最主要的变化是Context,还有就是废除了几个生命周期,比如ComponentWillReceiveProps(说实话,实际项目中,这个生命周期完全可以用ComponentWillUpdate来替换)

  6. React16.3中的refs不再推荐直接传递一个函数了,而是使用了全新的React.createRef来替代。当然以前的方法依旧适用,毕竟是为了兼容。

开始使用

React.createContext

createContext用来创建一个Context,它接受一个参数,这个参数会作为Context传递的默认值。需要注意的是,如果你传入的参数是个对象,那么当你更改Context的时候,内部会调用Object.is来比较对象是否相等。这会导致一些性能上的问题。当然,这并不重要,因为大部分情况下,这点儿性能损失可以忽略。

我们看下这个例子,这是一个提供主题(Light/Dark)类型的Context

// context.jsimport * as React from 'react';// 默认主题是Lightexport const { Provider, Consumer } = React.createContext("Light");复制代码

接下来我们只需要在需要的文件里import就行了

Provider

Provider是需要使用Context的所有组件的根组件。它接受一个value作为props,它表示Context传递的值,它会修改你在创建Context时候设定的默认值。

import { Provider } from './context';import * as React from 'react';import { render } from 'react-dom';import App from './app';const root = (    
);render(root, document.getElementById('root'));复制代码

Consumer

Consumer表示消费者,它接受一个render props作为唯一的children。其实就是一个函数,这个函数会接收到Context传递的数据作为参数,并且需要返回一个组件。

// app.jsximport { Consumer } from './context';import * as React from 'react';export default class App extends React.Component {    render() {        return (            
{ theme =>
Now, the theme is { theme }
}
) }}复制代码

一些需要注意的地方

多层嵌套

Context为了确保重新渲染的快速性,React需要保证每个Consumer都是独立的节点。

const ThemeContext = React.createContext('light');const UserContext = React.createContext();function Toolbar(props) {  return (    
{theme => (
{user => (
)}
)}
);}class App extends React.Component { render() { const {signedInUser, theme} = this.props; return (
); }}复制代码

当层次更加复杂的时候,会变得很烦人。因此推荐当层次超过两层之后,创建一个自己的render prop或许是个不错的主意。在实际工程中,其实并不建议多层嵌套。更为适合的时,提供一对ProvierConsumer对,传递状态管理工具对应的实例就行了。

在生命周期中使用

在之前的Context API中,在一些声明周期中会暴露一个context的参数,以供开发者更为方便的访问。新版API并没有这个参数传递了,更为推荐的方式是直接把Context的值通过props传递给组件。具体来说,就像下面这个官方的例子这样。

class Button extends React.Component {  componentDidMount() {    // ThemeContext value is this.props.theme  }  componentDidUpdate(prevProps, prevState) {    // Previous ThemeContext value is prevProps.theme    // New ThemeContext value is this.props.theme  }  render() {    const {theme, children} = this.props;    return (          );  }}export default props => (  
{theme =>
}
);复制代码

不像以前那样,可以直接通过this.context访问,新版本的Context只能在render方法里面访问。因为Context只暴露在Consumerrender prop里面。个人觉得这是这个版本API的一个缺点。所以只有采用上面这种折中的方式,再包装一个函数组件来封装到props里面去。相比较而言,还是麻烦了一点儿。在组件树里面多了一个函数组件,也是一个缺点。

Consumer封装

当一个Context的值多个组件都在使用的时候,你需要手动地每次都写一次Consumerredner prop。这是很烦的,程序员都是很懒的(至少我是这样),因此这个时候利用一下React的HOC来封装一下来简化这个过程。

const ThemeContext = React.createContext('light');function ThemedButton(props) {  return (    
{theme =>
}
);}复制代码

接下来,当你需要使用Context的时候,就不需要在写什么Consumer

export default props => (    ThemeButton(props));复制代码

转发refs

当你封装完一个Consumer之后,或许你想要用ref来获取Consumer里面根组件的实例或者对应的DOM。如果直接在Consumer上使用ref,是得不到想要的结果的。于是在React16.3里面,使用了一种全新的技术(不确定是不是16.3才引入的),叫做转发refs 。不仅仅用在Context里面,实际上,在任何你想要把ref传递给组件内部的子组件的时候,你都可以使用转发refs

具体来说,你需要使用一个新的API:React.forwardRef((props, ref) => React.ReactElement),以下面这个为例:

class FancyButton extends React.Component {}// Use context to pass the current "theme" to FancyButton.// Use forwardRef to pass refs to FancyButton as well.export default React.forwardRef((props, ref) => (  
{ theme => (
) }
));复制代码

React.forwardRef()接受一个函数作为参数。实际上,你可以将这个函数当做一个函数组件,它的第一个参数和函数组件一样。不同的地方在于,它多了一个ref。这意味着如果你在React.forwardRef创建的组件上使用ref的话,它并不会直接被组件消化掉,而是向内部进行了转发,让需要消化它的组件去消化。

如果你觉得难以理解,其实这种方法完全可以用另一种方法替代。我们知道,在React中,ref并不会出现在props中,它被特殊对待。但是换个名字不就行了吗。

需要提一下的是,以前我们获取ref是传递一个函数(不推荐使用字符串,这是一个历史遗留的问题,ref会在某些情况下无法获取到正确的值。vuejs可以使用,不要搞混了)。但是这个过程很烦的,我们只需要把实例或者DOM赋值给对应的变量就行了,每次都写一下这个一样模板的代码,很烦人的好吗。“千呼万唤”中,React终于听到了。现在只需要React.createRef就可以简化这个过程了。

class MyComponent extends React.Component {    constructor(props) {        super(props);        this.myRef = React.createRef();    }    render() {        return 
; }}复制代码

使用方法就这么简单,没什么特别的地方。

回到上面的话题,现在我们用props来实现转发refs的功能。

class Input extends React.Component {    reder() {		return (								)    }}function forwardRef(Component, ref) {	return (
);}// 使用forwardReflet input = React.createRef();forwardRef(Input, input);// 当组件绑定成功之后 input.current.focus();复制代码

React.createRef返回的值中,current属性表示的就是对应的DOM或者组件实例。forwardRef并没有什么特殊的含义,就是一个简单的props。这个用法就像是状态提升一样。

你可能感兴趣的文章
做固定比例的页面
查看>>
微服务架构
查看>>
数字信号处理的思考
查看>>
Java接口和抽象类用法总结
查看>>
浅析Java中的final关键字
查看>>
通过B表字段更新A表
查看>>
Matlab常用函数(1)
查看>>
19. Remove Nth Node From End of List C++删除链表的倒数第N个节点
查看>>
【原】centos系统命令部分不可用
查看>>
sqllocaldb
查看>>
因果图法设计测试用例
查看>>
BZOJ4807:車(组合数学,高精度)
查看>>
sas中的sql(8)sql选项解析,数据字典
查看>>
BZOJ4567:[SCOI2016]背单词——题解
查看>>
洛谷3676:小清新数据结构题——题解
查看>>
sed 指定行之间的内容替换
查看>>
MQTT协议简记
查看>>
[转] xgboost
查看>>
[转载]什么是“成功的项目”:谈谈软件交付价值
查看>>
easyui datagrid 动态表头配置
查看>>