到底怎么用React的Provider

10,008 阅读4分钟

不想看吐槽的,可以直接略过,查看正题。

为什么要用Provider

内部项目,把全局状态放在了3个地方管理

  1. window -好像很有道理的样子
  2. localStorage -老铁没毛病
  3. url -我也找不到什么问题

问题不是存取状态的方式,而是把3面3种方法用了一个遍。嗯,我承认,我佛了。整体项目代码中充斥着这样的代码,据说这项目还是阿里开源的,我佛气!贴个图,口说无凭

constructor(props) {
    super(props);
    this.deleteDialog = React.createRef();
    this.showcode = React.createRef();
    this.field = new Field(this);
    this.appName = getParams('appName') || getParams('edasAppId') || '';
    this.preAppName = this.appName;
    this.group = getParams('group') || '';
    this.preGroup = this.group;
    this.dataId = getParams('dataId') || '';
    this.preDataId = this.dataId;
    this.serverId = getParams('serverId') || 'center';
    this.edasAppId = getParams('edasAppId') || '';
    this.edasAppName = getParams('edasAppName') || '';
  }
}
      

getParams 是什么? 从URL中获取对应参数的值,你没猜错,如果要加一个全局状态变量还有一个setParams方法。大佬就是考虑周全。

用惯了React-Router的你也许会问: 为什么参数不从match中去取,来来来,我们看一下下面的代码。

const MENU = [
  { path: '/', exact: true, render: () => <Redirect to="/configurationManagement" /> },
  { path: '/namespace', component: Namespace },
  { path: '/newconfig', component: Newconfig },
  { path: '/configsync', component: Configsync },
  ...
]

你猜的没错,整体路由没有任何参数匹配,别跟我谈什么match, url又不是不能用。🌹🐔

这时候,维护全局状态变量的大任就交给我们伟大的url了。

用url做状态维护代价是什么?

意味着我要无时不刻去监听url的状态,去取我想要的对应参数,再去更新对应的值。运气不好可能还要去更新一下window或者更新一下localStorage(又不是不能用)
所以项目我开出4天排期解决统一状态混乱的被压缩到了1天。嗯。又不是不能用。

正题,使用Provider

知道我上面的痛苦之后,排期又只给我一天之后,那么我的解决方案也应运而生了。Provider 万岁!
(题外话,项目里面用到了react-redux,而且也用了Provider,都没发挥他们应有的价值就是了)

Provider API简析

  • React.createContext 创建Context
  • Context.Provider 使用他的时候,修改value属性值,可实现动态变更Context
  • Class.contextType 只有用了他,才能在this.context 中取到Context的值
  • Context.Consumer 使用这个方法,才能实现组件订阅多个Context 到底怎么用

静态传值

静态传值就没什么讲究的了,调用 React.createContext(defaultValue) , 直接写defaultValue就可以正常传值。

动态变更

动态的变更往往不是在一个文件中去操作context,这时候需要在一个第三方文件中创建context。如下nav-context.js 文件

import React from 'react';
export const NavContext = React.createContext({
handleNavChange: () => {},
});

然后在需要使用该context的文件中import。并变更其 value,参考官方例子

<ThemeContext.Provider value={this.state.theme}>
          <Toolbar changeTheme={this.toggleTheme} />
  </ThemeContext.Provider>

这时候,context的默认值就被设置为了this.state.theme。这里可以扩展到,传递事件,这样在其他组件中,就可以调用该事件。 实际引用文件中同样是import该context,这里需要注意的是,如何让组件取到对应的context。有2种方法

  1. 利用Class.contextType 让 一个Context对象挂在到组件中,使用this.context可以取到对应的值。(这样的方法,组件只可订阅单一的context)
	class Demo extends React.Component {
	static contextType = NavContext;
	}
  1. 利用Context.Consumer, 注意该api。children需要传入的是一个函数,参数即context的value
return <NavContext.Consumer>{this.renderDOM}</NavContext.Consumer>;

使用这种方法,则传值需要在DOM中直接用到,如果需要在组件方法中使用,则需要把对应的值绑定到组件实例上。

	renderDOM = ({ handleNavChange }) => {
	this.handleNavChange = handleNavChange;

使用Tips

  1. 使用Context组件内更新Context,可以在Context对象中配置回调方法,让组件内调取该方法更新Context
	<NavContext.Provider value={{ color: this.state.color, setColor: (color) => this.setState({ color }) }}>
	</NavContext.Provider>

其实官方文档讲的挺清楚的。。链接付上

getParams 和 setParams

这段代码还算可取,贴一贴

/**
 * 获取url中的参数
 */
const getParams = (function(_global) {
  return function(name) {
    const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
    let result = [];
    if (_global.location.hash !== '') {
      result = _global.location.hash.split('?'); // 优先判别hash
    } else {
      result = _global.location.href.split('?');
    }

    if (result.length === 1) {
      result = _global.parent.location.hash.split('?');
    }

    if (result.length > 1) {
      const r = result[1].match(reg);
      if (r != null) {
        return decodeURIComponent(r[2]);
      }
    }

    return null;
  };
})(global);

/**
 * 设置参数
 */
const setParams = (function(global) {
  let _global = global;
  const _originHref = _global.location.href.split('#')[0];
  return function(name, value) {
    if (!name) {
      return;
    }

    let obj = {};
    if (typeof name === 'string') {
      obj = {
        [name]: value,
      };
    }

    if (Object.prototype.toString.call(name) === '[object Object]') {
      obj = name;
    }

    let hashArr = [];
    if (_global.location.hash) {
      hashArr = _global.location.hash.split('?');
    }

    const paramArr = (hashArr[1] && hashArr[1].split('&')) || [];

    let paramObj = {};
    paramArr.forEach(val => {
      const tmpArr = val.split('=');
      paramObj[tmpArr[0]] = decodeURIComponent(tmpArr[1] || '');
    });
    paramObj = Object.assign({}, paramObj, obj);

    const resArr =
      Object.keys(paramObj).map(key => `${key}=${encodeURIComponent(paramObj[key] || '')}`) || [];

    hashArr[1] = resArr.join('&');
    const hashStr = hashArr.join('?');
    if (_global.history.replaceState) {
      const url = _originHref + hashStr;
      _global.history.replaceState(null, '', url);
    } else {
      _global.location.hash = hashStr;
    }
  };
})(global);