41

Writing Redux in 15 lines of code

 5 years ago
source link: https://www.tuicool.com/articles/hit/yQruaez
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

I was inspired to write this today after a colleague new to React complained about how much boilerplate is involved when using Redux (and Redux-Saga). So I put this together for a couple reasons:

  1. We mistakenly think verbosity is necessary for a one-way data-flow.
  2. We use Redux without understanding how it works.

For our simple Redux store we want the following features:

  • A function to update the store state (because we need to notify subscribers when the state changes) —  update(storeKey, updateFn)
  • A function to subscribe to updates —  subscribe(fn)
  • A function to get the internal state (this isn’t necessary but encourages the user to not directly manipulate the state object) —  getState()
class Store {
  constructor(initialState) {
    this.state = initialState;
    this.subscriptions = [];
  }
  update(storeKey, updateFn) {
    const nextStoreState = updateFn(this.state[storeKey]);
    if (this.state[storeKey] !== nextStoreState) {
      this.state[storeKey] = nextStoreState;
      this.subscriptions.forEach(f => f(store));
    }
  }
  subscribe = fn => this.subscriptions.push(fn)
  getState = () => this.state
}

note the lack of line breaks to preserve my catchy title

Pretty simple right? Now each time the store updates we want to call ReactDOM.render to re-render our app. Later we could add a HOC like redux-connect does to wrap our root component to handle the updates, and to pass store props anywhere in our app (part II anyone?).

Let’s see that working in a simple demo.

You might have noticed the following method:

increaseUserAge = () => {
  this.props.store.update('user', state => ({
    ...state,
    age: state.age + 2,
  }));
}

Look mother, no actions!

Yet the data still flows one-way. And there’s nothing stopping us from extracting updates out into re-usable and easily testable functions:

// Somewhere else.
function fetchUserDetailsAction(store) {
  const user = store.getState();
if (!user.fetched) {
    $get('/user/123').then(user => store.update('user', user));
  }
}
// In your Component
componentDidMount() {
  fetchUserDetailsAction(this.props.store);
}

But I like reducers and actions :@

As demonstrated above, complicated state updates can be extracted without needing actions. But I like reducers too. They keep data manipulation for each store node in one place.

There’s nothing stopping us from have a reducers/user.js file:

const userReducer = {
  increaseUserAge: (state, ageIncrease) => ({
    ...state,
    age: state.age + ageIncrease,
  }),
  setUserEmail: (state, email) => ({
    ...state,
    email,
  }),
};

And we could use that instead:

increaseUserAge = () => {
  userReducer.increaseUserAge(this.props.store, 2);
}

I wanted to demonstrate some basic principles here and show that you can have a very lean store-update-cycle if you choose to.

Our next step would be adding a connect() function so we can wrap our app in a HOC and so we can pass pieces of state as props to components like redux-connect does.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK