以前的版本中 Context API 是作为未公开的实验性功能存在的,随着越来越多的声音要求对其进行完善,在 v16.3 版本,React 团队重新设计并发布了新的官方 Context API。
使用 Context API 可以更方便的在组件中传递和共享某些 "全局" 数据,这是为了解决以往组件间共享公共数据需要通过多余的 props 进行层层传递的问题 (props drilling)。比如以下代码:
const HeadTitle = (props) => { return ( <Text> {props.lang.title} </Text>; ); }; // 中间组件 const Head = (props) => { return ( <div> <HeadTitle lang={props.lang} /> </div> ); }; class App extends React.Component { render() { return ( <Head lang={this.props.lang} />; ); } } export default App = connect((state) => { return { lang:state.lang } })(App);我们为了使用一个语言包,把语言配置存储到一个 store 里,通过 Redux connect 到顶层组件,然而仅仅是最底端的子组件才需要用到。我们也不可能为每个组件都单独加上 connect,这会造成数据驱动更新的重复和不可维护。因此中间组件需要一层层不断传递下去,就是所谓的 props drilling。
对于这种全局、不常修改的数据共享,就比较适合用 Context API 来实现:
首先第一步,类似 store,我们可以先创建一个 Context,并加入默认值:
const LangContext = React.createContext({ title:"默认标题" });然后在顶层通过 Provider 向组件树提供 Context 的访问。这里可以通过传入 value 修改 Context 中的数据,当value变化的时候,涉及的 Consumer 内整个内容将重新 render:
class App extends React.Component { render() { return ( <LangContext.Provider value={this.state.lang} > <Head /> </LangContext.Provider> ); } }在需要使用数据的地方,直接用 Context.Consumer 包裹,里面可以传入一个 render 函数,执行时从中取得 Context 的数据。
const HeadTitle = (props) => { return ( <LangContext.Consumer> {lang => <Text>{lang.title}</Text> } </LangContext.Consumer> ); };之后的中间组件也不再需要层层传递了,少了很多 props,减少了中间漏传导致出错,代码也更加清爽:
// 中间组件 const Head = () => { return ( <div> <HeadTitle /> </div> ); };那么看了上面的例子,我们是否可以直接使用 Context API 来代替掉所有的数据传递,包括去掉 redux 这些数据同步 library 了?其实并不合适。前面也有提到,Context API 应该用于需要全局共享数据的场景,并且数据最好是不用频繁更改的。因为作为上层存在的 Context,在数据变化时,容易导致所有涉及的 Consumer 重新 render。
比如下面这个例子:
render() { return ( <Provider value={{ title:"my title" }} > <Content /> </Provider> ); }实际每次 render 的时候,这里的 value 都是传入一个新的对象。这将很容易导致所有的 Consumer 都重新执行 render 影响性能。
因此不建议滥用 Context,对于某些非全局的业务数据,也不建议作为全局 Context 放到顶层中共享,以免导致过多的 Context 嵌套和频繁重新渲染。
五、Ref API除了 Context API 外,v16.3 还推出了两个新的 Ref API,用来在组件中更方便的管理和使用 ref。
在此之前先看一下我们之前使用 ref 的两种方法。
// string命名获取 componentDidMount(){ console.log(this.refs.input); } render() { return ( <input ref="input" /> ); } // callback 获取 render() { return ( <input ref={el => {this.input = el;}} /> ); }前一种 string 的方式比较局限,不方便于多组件间的传递或动态获取。后一种 callback 方法是之前比较推荐的方法。但是写起来略显麻烦,而且 update 过程中有发生清除可能会有多次调用 (callback 收到 null)。
为了提升易用性,新版本推出了 CreateRef API 来创建一个 ref object, 传递到 component 的 ref 上之后可以直接获得引用:
constructor(props) { super(props); this.input = React.createRef(); } componentDidMount() { console.log(this.input); } render() { return <input ref={this.input} />; }另外还提供了 ForwardRef API 来辅助简化嵌套组件、component 至 element 间的 ref 传递,避免出现 this.ref.ref.ref 的问题。
例如我们有一个包装过的 Button 组件,想获取里面真正的 button DOM element,本来需要这样做:
class MyButton extends Component { constructor(props){ super(props); this.buttonRef = React.createRef(); } render(){ return ( <button ref={this.buttonRef}> {props.children} </button> ); } } class App extends Component { constructor(props){ super(props); this.myRef = React.createRef(); } componentDidComponent{ // 通过ref一层层访问 console.log(this.myRef.buttonRef); } render(){ return ( <MyButton ref={this.myRef}> Press here </MyButton> ); } }