你可能不知道的React用法🔥

9,261 阅读4分钟

前言

React作为前端最🔥的框架之一,但是有的时候我们仅限于能用的阶段,有一些高级用法,我们在日常开发中却很少涉足。但是一旦用起来,我们就能发现它的方便和强大之处,我们就会越来越发现我们已经离不开它了!这就像是刚用React时,我内心是拒绝的,但是现在我已经离不开它了,越来越不能理解以前自己为什么抱着JQuery不放呢!

今天我们重点讲一下Context这个高级API,以及如何封装它,让它更加易用!

Context简介

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。

在一个典型的 React 应用中,数据是通过props属性由上向下(由父及子)的进行传递的,但这对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI主题),这是应用程序中许多组件都所需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props 。

简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。

假设我们有一种场景,我们有一个业务容器App,里面有一个组件容器Container,Container组件内包含一个Form表单,Form表单里面有一个提交按钮SubmitButton。假如使用props传递,我们就不得不传递四层。

看到了吗?很方便吧!这里我们使用Context,在组件可以直接通过context获取最顶层绑定的值,避免了一层层传递props的麻烦,也减少出错的可能性。

如何使用Context

如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式。

Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。例如,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式,使用context,我们可以避免通过中间元素传递props。

// 创建一个 theme Context,  默认 theme 的值为 light
const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  // ThemedButton 组件从 context 接收 theme
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

// 中间组件
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

一种更简单的使用方式

看了上面的使用方式,有没有觉得还是有一些不爽,有没有简单一点的方式呢,或者能不能帮我封装一下呢? 当然可以,Javascript工程师是无所不能的!!!

首先是我们的provider.js,这个就是我们封装的context使用工具

import React, { Component } from 'react';

export const Context = React.createContext();

export class ContextProvider extends Component {
    render() {
        return (
            <Context.Provider value={this.props.context}>
                {this.props.children}
            </Context.Provider>
        );
    }
}

/**
 * 用注解的方式给子组件注入属性
 */

export const injectContext = (contexts) => RealComponent => {
    return class extends Component {
        render() {
            return (
                <Context.Consumer>
                    {context => {
                        // 将顶层的context分发到各层
                        let mapContext = {};
                        if(Array.isArray(contexts)) {
                            contexts.map(item => {
                                mapContext[item] = context[item];
                            });
                        }
                        return (
                            <RealComponent {...mapContext} {...this.props} />
                        )
                    }}
                </Context.Consumer>
            );
        }
    };
};

还是举个栗子,来让大家明白上述封装的方法的方便之处。 假如要实现GrandParent -> Parent -> Son,从GrandParent组件传递属性到GrandSon组件,每个组件都有一个独立的文件。

先看入口文件,我们在入口文件进行绑定上下文,使用provider里面的ContextProvider类,这里我们主要绑定了propA和propB。

// 入口文件

import React, { PureComponent } from 'react';
import { ContextProvider } from './provider';
import GrandParent from './GrandParent';

class Index extends PureComponent {
    render () {
        return (
            <ContextProvider context={{
                propA: 'propA',
                propB: 'propB'
            }}>
                <GrandParent />
            </ContextProvider>
        )
    }
}

Parent组件没什么特殊的

import React, { PureComponent } from 'react';
import Son from './Son';

class Index extends PureComponent {
    render () {
        return (
            <Son />
        )
    }
}

Son组件是真正使用属性propA和propB的地方,我们通过ES6的Decorator实现,非常方便。注入后,可以像props一样使用。

import React, { PureComponent } from 'react';
import { injectContext } from './provider';
import Son from './Son';

@injextContext(['propA', 'propB'])
class Index extends PureComponent {
    render () {
        return (
            <div>
                <span>propA为{this.props.propA}</span>
                <span>propB为{this.props.propB}</span>
            </div>
        )
    }
}

后记

在这一小节中,我们主要讲了React的一个高级语法Context,而且为了使用方便,我们封装了ContextProvider类和injextContext方法,使用时利用ES6的Decorator语法糖,非常简便。大家在日常开发中,也可以封装出一些这样的小工具,可以极大提升开发效率。

最后元旦快到了,祝大家新年快乐!!!

@Author: WaterMan