The three Principles of Redux, the principles of createSore and their realizations — Cos blog — WA cried in one word

To be clear, while Redux is a great tool for managing state, consider whether it is appropriate for your scenario.

Don’t use Redux just because someone says you should, but take some time to understand the potential benefits and trade-offs of using it.

Open new pit… Take notes and transcriptions from the Redux Chinese website, the Redux Introduction video series and its tutorials.

This article is the 1-8 video in the following teaching video, which explains the three principles of Redux and the principles and implementation of Reducer, (getState, Dispatch, Subscribe) and createStore in Redux, and realizes a simple counter. Basically, after watching Redux, you have a general understanding

Why learn pinching, mainly because recently read the project more or less have Redux use, do not learn to read the root do not understand

Redux founder Dan Abramov presents concepts in 30 short films (2-5 minutes). The linked Github repository contains notes and transcriptions of the video. Redux Introduction series video notes and transcriptions

Introduction to the

What is the story

Redux is a state container for JavaScript applications that provides predictable state management.

This allows you to develop stable and predictable applications that run in different environments (client, server, native application) and are easy to test. Not only that, it also provides a great development experience, such as a time travel debugger that can be edited in real time.

Redux also supports other interface libraries in addition to React. It is small and compact (only 2kB, including dependencies), but has a strong plugin extension ecosystem.

What needs to use Redux

To be clear, while Redux is a great tool for managing state, consider whether it is appropriate for your scenario. Don’t use Redux just because someone says you should – take some time to understand the potential benefits and trade-offs of using it.

It is recommended to start using Redux when you encounter the following problems in your own projects:

  • You have a lot of data that changes over time
  • You want states to have a single source of truth.
  • You find that managing all state in a top-level component is not maintainable

Redux three principles

Principle 1: Single immutable state tree

Redux: The Single Immutable State Tree from @dan_abramov on @eggheadio

The Single Immutable State Tree is a mutable State Tree.

The first principle of Redux is that the entire state of an application will be represented by a JavaScript object, whether the application is simple and small or complex with a lot of UI and state changes. This JavaScript object is called a state tree.

Here are the possible states in a Todo application:

"current_state:"
[object Object] {
  todos: [[object Object] {
    completed: true.id: 0.text: "hey",
  }, [object Object] {
    completed: false.id: 1.text: "ho",}],visibilityFilter: "SHOW_ACTIVE"
}
Copy the code

Principle 2: The state tree is read-only

Redux: Describing State Changes with Actions from @dan_abramov on @eggheadio

The second principle of Redux is that the state tree is read-only. There is no way to modify or write it directly, only to modify it by ** “initiate Action” **.

Action is a normal JS object that describes changes. It is the smallest representation of changes made to the data. Its structure is entirely up to us, with the only requirement that it must have a binding property type (usually a string, since it is serializable).

dispatch an action

For example, in a Todo application, the component that displays Todo doesn’t know how to add items to the list, all they know is that they need to dispatch an action that has a type of type:” Add Todo “, a Todo text and an ordinal number.

If you switch a task, again, the component doesn’t know how it happened. All they need todo is dispatch an action with type, todo, and pass in the ID of the todo they want to switch to.

As you can see above, the state is read-only and can only be changed by a Dispatch operation.

Principle 3: To describe state changes, you must write a pure function (Reducer)

Redux: Pure and Impure Functions | egghead.io

Redux: The Reducer Function | egghead.io

And Redux’s third principle is: To describe state changes, you have to write a pure function that takes the previous state of the application and the initiated action (the Action being dispatched) and returns the next state of the application. This pure function is called Reducer.

Understand pure and impure functions

First we need to understand what a pure/impure function is, because Redux sometimes requires us to write pure functions.

I also mentioned it in my blog before: [the second youth Training camp – winter vacation front] – “Learn JavaScript with moonlight shadow” notes, here again.

A pure function is one in which the result of a function depends only on its parameters and has no side effects during execution, which means that it has no effect on the outside world.

It is safe to say that calling a pure function with the same set of arguments will return the same value. They are predictable.

In addition, pure functions do not modify the values passed to them. For example, the squareAll function that accepts an array does not overwrite the items in the array. Instead, it returns a new array using an item map.

Examples of pure functions:

function square(x) {
    return x*x;
}
function squareAll(items) {
    return items.map(square);	// Notice that a new array is generated instead of returning items directly
}
Copy the code

Examples of impure functions:

function square(x) {
  updateXInDatabase(x);	// It also affects x in the database
  return x * x;
}
function squareAll(items) {
  for (let i = 0; i < items.length; i++) {
    items[i] = square(items[i]);	// And directly modify items...}}Copy the code

The concept of immutability can be useful in many scenarios. For example, the time travel function in tic-tac-toe implementation of React official document. If each step is transformed on the original object, undo will become very complicated.

Reducer

React pioneered the idea that the UI layer is most predictable when it is described as a pure function of application state.

Redux complements this approach with another idea: state mutations in an application must be described by a pure function that takes the previous state and the operation being scheduled, and returns the next state of the application.

Even in large applications, there is still only one function needed to calculate the new state of the application. It performs this action based on the previous state of the entire application and the operation being scheduled.

However, this operation does not have to be slow. If parts of the state have not changed, their references can remain as they are. That’s what makes Redux fast.

Here is a complete example

The initial state

In the initial state, toDOS is null and the filter shows all

Add a Todo

The change is shown here: In the initial state, todos has no content and the filter shows everything. Todos has an extra TODO in the state after the action is initiated, and the filtering view remains unchanged

Complete the Todo

Click on a todo and set it to complete. You can see that when the action is initiated, the todos text is unchanged and the state complete is set to complete…

Changing filtering views

After adding a todo, click the Active filter and observe the state before and after it. It can be found that the status of visibilityFilter has changed from “SHOW_ALL” to “SHOW_ACTIVE”, but the content of toDOS remains unchanged (abcd has not been deleted).

Write a Reducer counter with tests

Redux: Writing a Counter Reducer with Tests | egghead.io

The first function we’ll write is a reducer for the counter example. We’ll also use Expect for assertions.

eggheadio-projects/getting-started-with-redux: null – Plunker (plnkr.co)

const counter = (state = 0, action) = > {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

expect(
  counter(0, { type: 'INCREMENT' })
).toEqual(1);

expect(
  counter(1, { type: 'INCREMENT' })
).toEqual(2);

expect(
  counter(2, { type: 'DECREMENT' })
).toEqual(1);

expect(
  counter(1, { type: 'DECREMENT' })
).toEqual(0);

expect(
  counter(1, { type: 'SOMETHING_ELSE' }) 
).toEqual(1);

expect(
  counter(undefined, {})
).toEqual(0);

Copy the code

As shown above, the Reducer counter set two identifiable types (INCREMENT and DECREMENT), which respectively represent count +1 and -1. If the state passed in is undefined when writing to the Reducer, an object (initState) representing the initial state needs to be returned. In the case of this counter, we return 0 because our count starts at 0. If the action passed in is not SOMETHING_ELSE that the Reducer can recognize, we only return the current state.

Storage methods: getState(), Dispatch (), and subscribe()

Redux: Store Methods: getState(), dispatch(), and subscribe() | egghead.io

This section uses the built-in functions in Redux. We introduced createStore using the ES6 destructor syntax.

const counter = (state = 0, action) = > {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      returnstate; }}const { createStore } = Redux; // Redux CDN import syntax
// import { createStore } from 'redux' // npm module syntax

const store = createStore(counter);
Copy the code

CreateStore creates a Redux store to store all the states in the application. There should be only one store in your app.

parameter

  1. Reducer (Function): Receive two parameters, the current state tree and the action to be processed, and return a new state tree.
  2. [preloadedState] (any): Initial state. In homogeneous applications, you can decide whether to post a state hydrate from the server, or recover one from a previously saved user session. If you usecombineReducerscreatereducerIt must be a normal object with the same structure as the keys passed in. Otherwise, you are free to pass in anyreducerComprehensible content.
  3. Enhancer (Function): Store enhancer, optional. The store can be enhanced with third party capabilities such as median pricing, time travel, and persistence. Is a high-order function that combines Store Creator and returns a new enhanced Store Creator. The only store enhander built into Redux is applyMiddleware().

The return value

(Store): an object that holds all the states of the application. The only way to change state is the Dispatch action. You can subscribe to listen for state changes and then update the UI.

The store created by createStore has three important methods

GetState () Gets the state

GetState () retrieves the current state of the Redux store. Returns the current state tree of the application. It is the same as the last reducer return value of the store.

Dispatch () initiates an action

Dispatch () is the most commonly used. The distribution of the action. This is the only way to trigger a state change.

It calls the Store’s Reduce function synchronously with the result of the current getState() and the action passed in. Its return value will be taken as the next state. From now on, this becomes the return value of getState(), and the change Listener is triggered.

The subscribe () to register

Add a change listener. Whenever a Dispatch action is executed, part of the state tree may have changed. You can call getState() in the callback function to get the current state.

You can dispatch() inside the change listener, but you need to be aware of the following:

  1. A listener call to dispatch() should only occur in response to the user’s actions or under special conditions (such as a Dispatch action if the store has a particular field). While there are no conditions under which it is technically possible to call dispatch(), it can lead to an endless loop with each dispatch() changing store.
  2. The Subscriptions store a snapshot before each dispatch() call. When you subscribe or unsubscribe while the listener is being called, it has no effect on the current dispatch(). But for the next dispatch(), nested or not, the most recent snapshot of the subscription list will be used.
  3. The subscriber should not notice all state changes, and the state often changes multiple times due to nested dispatches () before the subscriber is called. Ensure that all listeners are registered before dispatch() starts so that the last state of the listener’s existence is passed in when the listener is called.

This is a low-level API. In most cases, you won’t use it directly, but some React (or other library) binding. If you want callback functions to be executed using the current state, you can write a custom observeStore tool. The Store is also an Observable, so you can subscribe to updates using libraries like RxJS.

If you need to unbind the change listener, simply execute the subscribe return function.

// ... `counter` reducer as above ...

const { createStore } = Redux;
const store = createStore(counter);

store.subscribe(() = > {
  document.body.innerText = store.getState();
});

document.addEventListener('click'.() = > {
    store.dispatch({ type : 'INCREMENT'})});Copy the code

The initial state is not updated because the render occurs after a callback to subscribe, but after a fix:

const render = () = > {
  document.body.innerText = store.getState();
};

store.subscribe(render);
render(); // Render the initial state 0 once, and render after each dispatch

document.addEventListener('click'.() = > {
    store.dispatch({ type : 'INCREMENT'})});Copy the code

The above official website excerpt from the document seems difficult to understand, look at the following simple implementation will understand.

Implement a simple version of createStore~!

Redux: Implementing Store from Scratch | egghead.io

In previous studies, we looked at how to use createStore(), but to better understand it, let’s write it from scratch!

So let’s go back to the previous case. We know that

  • The createStore function receives a Reducer function that returns the current state and is called by the internal Dispatch function

  • The createStore function creates a store that has two variables:

    • stateCurrent state, it’s a JavaScript object,
    • listenersListener array, which is a function array object
  • The createStore function creates a store that needs to have three methods: getState, Dispatch, and Subscribe

    • getStateMethod returns the current state
    • dispatchThe function is the only way to change the internal state by passing in an action by passing in the internal current state and actionreducerFunction (the createStore entry) to calculate the new state. After the update, we notify each change listener (by calling them) of dispatch
    • subscribePass a listener function as an argument and place it in the internal listener array. To unsubscribe the event listener,subscribeI need to return a function,Call this returned function to cancel the listeningThis function goes through internallyfilter()Assigns the Listeners array to a new listeners array (with the same reference as the current listeners removed).subscribe
    • On his return tostore, we need to fill the initial state. We need to assign a fake oneactionTo make thereducerReturns the initial value.
const createStore = (reducer) = > {	// Return store, can call getState, dispatch, subscribe
	let state;	
    let listeners = [];
    const getState = () = > state;   // The current state can be obtained externally by calling getState

    const dispatch = (action) = > {
        state = reducer(state, action); // Because reducer is a pure function, the state returned each time will not modify the original state~
        listeners.forEach(listener= > listener());     // In the dispatch event, call listeners ~ when reducer is successfully called
    }

    const subscribe = (listener) = > {   // Add listener!
        listeners.push(listener);
        return () = > {      // To be able to cancel the listener, return a function
            listeners = listeners.filter(l= >l ! == listener); } } dispatch({});// To give state an initial state!

    return { getState, dispatch, subscribe };
};
Copy the code

Improved counter

Redux: Implementing Store from Scratch | egghead.io

Improved example: Counter (cos)

Above, we implemented a Reducer of the counter. Here we improved it and actually rendered it with React

Writing the Counter component

Write a Counter component that is a “dumb” component. It does not contain any business logic.

The dumb component only specifies how to render the current state into the output and how to bind the callback function passed through prop to the event handler.

Components / / Counter
const Counter = ({ value, onIncrement, onDecrement, onElse }) = > (
  <div>
    <h1>{value}</h1>
    <button onClick={onIncrement}>+</button>
    <button onClick={onDecrement}>-</button>
    <button onClick={onElse}>else</button>
  </div>
);
// The component's rendering function
const render = () = > {
  console.log("render!");
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={()= > store.dispatch({
        type:'INCREMENT'
      })}
      onDecrement={() => store.dispatch({
        type:'DECREMENT'
      })}
      onElse={() => store.dispatch({
        type:'else'
      })}
      />.document.getElementById('root')); }Copy the code

When the dummy component is rendered, we specify that its value should be taken from the current state stored by Redux. When a user presses a button, the corresponding action is dispatched to Redux’s store.

Call createStore to add a listener

Call createStore to create a store, and call Store. Subscribe to add a Render listener

const counter = (state = 0, action) = > {
  console.log("now state:", state);
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      returnstate; }}// store create with counter reducer
const { createStore } = Redux;
const store = createStore(counter);
store.subscribe(render);  // Add a listener and re-render every status update
render();
Copy the code

Reducer specifies how to calculate the next state based on the current state and incoming actions. Finally, we subscribe to the Store (passing in the render function) so that the render function runs on each call to Dispatch to change state.

Ps: Finally, we subscribe to the Redux store so our render() function runs any time the state changes so our Counter gets the current Render is triggered every time the state changes. The createStore implementation will trigger the state even if the value does not change. Each dispatch will render every time the action is sent. Redux requires that every dispatch has an intention to change the state, and I’ll come back to that later in the video if it’s mentioned.)

Story: the React Counter example | egghead. IO

Check the process

To get an idea of this process, use the following example:

Let’s try: Counter (cos)

The initial value

Do nothing at first, we can see that a dispatch was made when createStore was created, and a render was made after the reducer (counter) function set state to an initial value of 0. (note)

Increase the count

Click + to see render called again

else

After clicking else several times, you see that each time it is rerendered, but the value of state looks the same, and the component does not appear to have changed

conclusion

To be clear, while Redux is a great tool for managing state, consider whether it is appropriate for your scenario.

  • Don’t use Redux just because someone says you should, but take some time to understand the potential benefits and trade-offs of using it.

After watching the 1-8 videos, we have basically understood when Redux is best to use and its disadvantages, the three principles of Redux and the principles and implementation of Reducer, (getState, Dispatch, Subscribe) and createStore in Redux. And implemented an extremely simple counter (incidentally knowing what dumb components are).

Redux’s Three principles:

  • The entire state of the application is represented by a JavaScript object called a state tree.
  • The state tree is read-only. There is no way to modify or write to it directly. You can only modify it indirectly by ** initiating Action **.
  • To describe state changes, you must write a pure function that takes the previous state of the application and the initiated action, and then returns the next state of the application. This pure function is called Reducer.

The createStore function receives a Reducer function that returns the current state and is called by the internal Dispatch function

  • The createStore function creates a store that has two variables:

    • stateCurrent state, it’s a JavaScript object,
    • listenersListener array, which is a function array object
  • The store created by the createStore function also needs to have these three methods: getState, Dispatch, and Subscribe

    • GetState returns the current state

    • The Dispatch function passes in an action and calculates the new state by passing the internal current state and action into the Reducer function (the input to the createStore). After the update, we notify each change listener (by calling them) of dispatch

    • Subscribe takes a listener function as an argument and puts it in the internal listener array. In order to unsubscribe an event listener, SUBSCRIBE needs to return a function that can be called to unsubscribe. Internally, this function assigns the Listeners array to a new listeners array via filter() (removing any new listeners returned with the same reference as the current listeners). subscribe

    • When we return to store, we need to populate the initial state. We will assign a dummy action to the Reducer to return the initial value.