In recent months, I have encountered some scenarios: there is no need for global state management, but page-level state management must be shared among some components. Introducing a state management library like Redux is a bit cumbersome, and it is not elegant to write it directly through props. The React version of the project is relatively new, so I tried the Context Api. The code looks like this:

// Context.js
const Context = React.createContext(
  {} // default value
)
export const Provider = Context.Provider
export const Consumer = Context.Consumer
Copy the code
// 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() {
    constval = { ... this.state,setName: this.setName
    }
    return (
      <Provider value={val}>
          <Page />
      </Provider>); }}Copy the code
// 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>
  );
}
Copy the code

There is no need to use a third-party state management library or pass props manually. This is not very flexible. Decorators can be used to simplify the writing of higher-order components such as providers and consumers. The result should be something like this:

// App.jsx
import React from 'react'
import { Provider } from './Context'

@Provider
export default class App extends React.Component{
    // State is not written here, it is extracted into the Context
}
Copy the code
// Page.jsx
import React from 'react'
import { Consumer } from './Context'

// The method passes a key array that needs a map to the properties in props. If not, all properties will be mapped
@Consumer(['list'.'query'])
export default class Page extends React.Component{
    render(){
        const { list, query } = this.props
        return(
            // ...)}}Copy the code

You can see that the Provider and Consumer are very concise, and of course it’s not the Provider and Consumer in the Context, and the maintenance of the state is removed. How does all this logic work? Code first:

// 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 = (a)= > {
    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() {
    constval = { ... 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> )Copy the code

Here is an example of a list of queries that can be wrapped so that the page can be loaded directly from props for any queries, page flips, or other operations. ProviderClass is the general logic for operating on state, which can be written according to personal habits.

The encapsulation of a Provider is relatively simple, but it can also be flexible. You can add a parameter, such as type, and then use @provider (type).

@consumer (), @consumer (‘name’), @consumer ([‘key1’, ‘key2’]), @consumer (val=>({name: Val.name})), after all, you want to be more flexible, and a more flexible Consumer is implemented later.

It’s a little bit more complicated, a little bit more complicated than the previous code, right? But as you can see, context.js is pretty generic. In different scenarios, just implement the state management part of the ProviderClass, and then pull out the Provider and Consumer parts a little bit and write a module. In the future, it will be better to import directly and use it directly. I always think so, but I have no time to implement it in the past few months. Every time, I copy YY/P and use it directly. Actually copy and paste is not that much trouble.

We recently had time to summarize the results of the implementation of the separation of state (just write a plain ES6 class), multi-provider scenarios, and more flexible use of providers and consumers. Without further discussion, let’s take a look at the final results:

// 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()
Copy the code
// 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 />)}}Copy the code
// 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>)}}Copy the code

And then, no then, it’s as simple as that. Of course, if you’re talking about being flexible, you can do whatever you want.

// Provider passes multiple stores
@Provider(store1, store2, store3)

// Map only the data and actions needed in Consumer to props
@Consumer('name'.'setName')

// Be more flexible?
@Consumer('userId',data => ({
  prov: data.addr.provvince,
  city: data.addr.city
}),'userName')

// Want Multi Context?
import { Provider, Consumer } from 'ctx-react' // Export a Provider and a Consumer by default
import Context as {Context: Context1, Provider: Provider1} from 'ctx-react'
import Context as {Context: Context2, Provider: Provider2} from 'ctx-react'

// There's some data in the Store that doesn't want to be propped up, and doesn't want to be passed to the Context?
import { exclude } from 'ctx-react'

class Store{
    name: 'zz',
  	@exclude temp: 'This field is not going into the Context'
}
Copy the code

Not this time. After all, it’s a hundred lines of code and a bike. However, some potential problems still need to be solved, and I will consider joining SCOOP.

As for how to implement, in fact, most of the Context encapsulation is similar to the above, for the part of state extraction, pay a little attention to the use of ES6 Proxy, when listening to the set triggered update, in addition, considering the state median value is an object, need to recursive Proxy.

The code has been dropped at github, github.com/evolify/ctx…

NPM: yarn add ctx-react

I thought I could play golang at leisure recently, but I got busy before I finished writing this article, so I decided to move bricks first.