基于React Context Api 和 Es6 Proxy的状态管理
栏目: JavaScript · 发布时间: 5年前
内容简介:近几个月的工作中,有遇到一些场景:基本不需要全局的状态管理,但页面级的,肯定需要在一些组件中共享,引入Redux这类状态管理库有点繁琐,直接通过props传递的话,写起来总觉得不是那么优雅。刚好项目中React版本比较新,就试了下Context Api,代码大致如下:以上是官方文档中给出的用法,好处在于不用借助第三方状态管理库,也不需要手动传递props,但看起来不是很灵活,其实对于Provider和Consumer这种高阶组件,我们可以借助decorators来简化写法,最后应该能到达一下这种效果:可以看
近几个月的工作中,有遇到一些场景:基本不需要全局的状态管理,但页面级的,肯定需要在一些组件中共享,引入Redux这类状态管理库有点繁琐,直接通过props传递的话,写起来总觉得不是那么优雅。刚好项目中React版本比较新,就试了下Context Api,代码大致如下:
// Context.js const Context = React.createContext( {} // default value ) export const Provider = Context.Provider export const Consumer = Context.Consumer
// App.jsx import {Provider} from './Context' import Page from './Page' class App extends React.Component { constructor(props) { super(props); this.state = { name: 'zz', } setName = name =>{ this.setState({name}) } } render() { const val = { ...this.state, setName: this.setName } return ( <Provider value={val}> <Page /> </Provider> ); } }
// View.jsx import React from 'react' import {Consumer} from './Context' export default class Page extends React.Component { return ( <Consumer> { val => ( <button onClick={val.setName}> {val.name} </button> )} </Consumer> ); }
以上是官方文档中给出的用法,好处在于不用借助第三方状态管理库,也不需要手动传递props,但看起来不是很灵活,其实对于Provider和Consumer这种高阶组件,我们可以借助decorators来简化写法,最后应该能到达一下这种效果:
// App.jsx import React from 'react' import { Provider } from './Context' @Provider export default class App extends React.Component{ // state 不写在这里,抽取到Context中 }
// Page.jsx import React from 'react' import { Consumer } from './Context' // 方法中传入需要map到props中的属性的key数组,如果不传,所有属性都会map @Consumer(['list', 'query']) export default class Page extends React.Component{ render(){ const { list, query } = this.props return( // ... ) } }
可以看到这里的Provider和Consumer很简洁,当然这也并非是Context中的Provider和Consumer,state状态的维护也抽离出去了,所有的这些逻辑是怎么实现的呢?先上代码:
// Context.js import React from 'react' import service from './service' const Context = React.createContext() class ProviderClass extends React.Component { constructor(props) { super(props) this.state = { list: [], keywords: null, pagination: { current: 1, total: 0, }, } } componentWillUnmount() { this.unmount = true } update = state => new Promise((resolve, reject) => { if (!this.unmount) { this.setState(state, resolve) } }) query = () => { const { keywords, pagination: { current }, } = this.state service.query(current, keywords).then(({ count, pageNo, list }) => { this.update({ list, pagination: { current: pageNo, total: count, }, }) }) } search = keywords => this.update({ keywords }).then(this.query) pageTo = (pageNo = 1) => { this.update({ pagination: { ...this.pagination, current: pageNo, }, }).then(this.query) } render() { const val = { ...this.state, query: this.query, pageTo: this.pageTo, search: this.search, } return ( <Context.Provider value={val}>{this.props.children}</Context.Provider> ) } } export const Provider => Comp => props => ( <ProviderClass> <Comp {...props} /> </ProviderClass> ) export const Consumer = keys => Comp => props => ( <Context.Consumer> {val => { let p = { ...props } if (!keys) { p = { ...p, ...val, } } else if (keys instanceof Array) { keys.forEach(k => { p[k] = val[k] }) } else if (keys instanceof Function) { p = { ...p, ...keys(val), } } else if (typeof keys === 'string') { p[keys] = val[keys] } return <Comp {...p} /> }} </Context.Consumer> )
这里已一个查询列表为例,这样封装了之后,不管是查询、翻页或者其他操作,页面上直接从props中取出来操作就行。ProviderClass中就是常规的操作state的逻辑,可以按照个人习惯来写。
Provider的封装也比较简单,但同时也可以很灵活,可以在前面再加个参数,比如type之类的,然后使用的时候: @Provider(type)
,总之,按自己的需求来写。
看起来Consumer的实现稍微复杂点,其实做的事情很简单,就是处理 @Consumer()
、 @Consumer('name')
、 @Consumer(['key1', 'key2'])
、 @Consumer(val=>({name: val.name}))
这几种情况,毕竟想要更灵活嘛,而且,后面还实现了一种更灵活的Consumer.
这么写好像更复杂了啊,比之前的代码还要多,还要难以理解?但你应该也发现了,这个Context.js可以说是一个通用的,在不同的场景,只需要实现ProviderClass中状态管理这部分就行了,然后就稍微把Provider和Consumer这两部分提取出来,写个module,以后直接import直接用就好了,一直这么想,可这几个月一直没时间去实现,每次都是 yy / p
拷贝过来直接用。其实复制粘贴也没那么麻烦(ーー゛)。
最近终于有时间来总结一下了,这次实现了state的分离(直接写一个普通的es6 class就行),以及多Provider的场景,而且Provider、Consumer的使用更灵活了,废话不多说,直接来看一下最后的成果:
// Store.js import axios from 'axios' class Store { userId: 00001 userName: zz addr: { province: 'Zhejiang', city: 'Hangzhou' } login() { axios.post('/login', { // login data }).then(({ userId, userName, prov, city }) => { this.userId = userId this.userName = userName this.addr.province = prov this.addr.city = city }) } } export default new Store()
// App.jsx import React from 'react' import {Provider} from 'ctx-react' import store from './Store' import Page from './Page' @Provider(store) export default class App extends React.Component { render(){ return( <Page /> ) } }
// Page.jsx import React from 'react' import {Consumer} from 'ctx-react' @Consumer export default class Page extends React.Component { render(){ const {userId, userName, addr:{province,city}, login} = this.props return( <div> <div className="user-id">{userId}</div> <div className="user-name">{userName}</div> <div className="addr-prov">{province}</div> <div className="addr-city">{city}</div> {/* form */} <button onClick={login}>Login</button> </div> ) } }
然后,没有然后了,就是这么简单。当然,既然说了要灵活,那就一定是你想怎样就怎样。
// Provider中传入多个Store @Provider(store1, store2, store3) // Consumer 中只map需要的data和action到props中 @Consumer('name', 'setName') // 再灵活一点? @Consumer('userId',data => ({ prov: data.addr.provvince, city: data.addr.city }),'userName') // 想要Multi Context ? import { Provider, Consumer } from 'ctx-react' // 默认导出一个Provider和一个Consumer import Context as {Context: Context1, Provider: Provider1} from 'ctx-react' import Context as {Context: Context2, Provider: Provider2} from 'ctx-react' // Store中有些数据不想要被代理,也不想传到Context中? import { exclude } from 'ctx-react' class Store{ name: 'zz', @exclude temp: '这个字段不会进入到Context中' }
这次真的没了, 毕竟也就一百来行代码,还要啥自行车。不过存在的一些潜在问题还是需要解决的,后续考虑加入scoop。
至于怎么实现的,其实大部分和上面对Context的封装差不多,对于state的抽离这部分稍微要注意点,用到了es6的Proxy, 在监听到set时触发更新,另外考虑到state中值为对象的情况,需要递归Proxy。
代码已丢到github, https://github.com/evolify/ctx-react
也发到了npm: yarn add ctx-react
本以为最近能闲下来玩一下golang,这篇文章还没写完就又忙起来了,算了算了,还是先搬砖吧。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端状态管理与有限状态机
- 为管理复杂组件状态困扰?试试 vue 简单状态管理 Store 模式
- 基于有限状态机的广告状态管理方案及实现
- 基于有限状态机的广告状态管理方案及实现
- vue状态管理演进
- 浅析前端状态管理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。