不想看吐槽的,可以直接略过,查看正题。
为什么要用Provider
内部项目,把全局状态放在了3个地方管理
- window -好像很有道理的样子
- localStorage -老铁没毛病
- 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种方法
- 利用Class.contextType 让 一个Context对象挂在到组件中,使用this.context可以取到对应的值。(这样的方法,组件只可订阅单一的context)
class Demo extends React.Component {
static contextType = NavContext;
}
- 利用Context.Consumer, 注意该api。children需要传入的是一个函数,参数即context的value
return <NavContext.Consumer>{this.renderDOM}</NavContext.Consumer>;
使用这种方法,则传值需要在DOM中直接用到,如果需要在组件方法中使用,则需要把对应的值绑定到组件实例上。
renderDOM = ({ handleNavChange }) => {
this.handleNavChange = handleNavChange;
使用Tips
- 使用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);