Getting Started with Redux Basics

Getting Started with Redux Basics

[TOCM]

Store

Container for storing data, there can only be one Store in the entire application, and the Store
can be generated through the creatStore function

import { createStore } from 'redux';
const store = createStore(fn);
//接受一个函数作为参数,返回新生成的 Store 对象
//可以有第二个可选参数,表示一个状态初始值

State

Represents a snapshot (state) of the Store at a certain moment.
A state corresponds to a view one-to-one .

import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();

Action

It is the notification object sent by the View to change the State. The only way to change the State is to use an Action, which will ship the data to the Store.

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

Action Creator

A method for generating Actions

const ADD_TODO = '添加 TODO';
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
const action = addTodo('Learn Redux');

store.getState()

Get a snapshot state of the current store

import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();

store.dispatch()

store.dispatch()Is the only way for the View to issue an Action.

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

In the above code, store.dispatch accepts an Action object as a parameter and sends it out.

store.subscribe()

Store allows you to set a listener function using the store.subscribe method, which is automatically executed once the State changes.

import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);

Obviously, as long as the update function of the View (for React projects, the render method or the setState method of the component) is put into listen, the automatic rendering of the View will be realized.

The store.subscribe method returns a function that can be called to cancel the listener.

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

Reducer

After the Store receives the Action, the calculation process of giving a new State is called Reducer. The
Reducer is a function that accepts the current State and Action as parameters and returns a new State.

const reducer = function (state, action) {
  // ...
  return new_state;
};

For example, a simple Reducer,

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default:
      return state;
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
});

In practice, there is no need to manually call the Reducer function, and the Reducer method will be automatically triggered when store.dispatch is executed. The Reducer method is passed as a parameter when the Store is created. Such as,

import { createStore } from 'redux';
const store = createStore(reducer);//传入reducer方法
//store.dispatch发送过来一个新的 Action,就会自动调用此Reducer

Why is this function called Reducer? Because it can be used as a parameter of the reduce method of the array. See the following example, a series of Action objects in order as an array.

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];
const total = actions.reduce(reducer, 0); // 3

Reducer is a "pure function", the same input must get the same output, which means that methods like Date.now() cannot be included in the method

The Reducer function cannot change the State, and must return a brand new object, please refer to the following writing method.

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
}

Simple implementation of Store

A simple implementation of the createStore method, you can see how the Store is generated.

const createStore = (reducer) => {
  let state;
  let listeners = [];//监听函数列表

  const getState = () => state;//获取当前的state

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  //设置监听函数
  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};

Reducer split

Split a huge reducer into multiple small reducers

const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload)
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload
      });
    default: return state;
  }
};

Such as splitting into three small Reducer functions

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
};

combineReducersmethod is used to combine small Reducer methods into one large Reducer

import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp;

You can put all sub-Reducers in one file, and then import them uniformly

import { combineReducers } from 'redux'
import * as reducers from './reducers'

const reducer = combineReducers(reducers)

A simple implementation of combineReducer

const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {}
    );
  };
};

Redux workflow

  1. The user issues an Action.
    store.dispatch(action);
  2. The Store automatically calls the Reducer and passes in two parameters: the current State and the received Action. Reducer will return a new State
    let nextState = todoApp(previousState, action);
  3. Once the State changes, the Store will call the listener function.
    // Set the listener function
    store.subscribe(listener);
    listener to get the current state through store.getState(). If you are using React, this can trigger a re-render of the View.
function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

middleware

Middleware is a function that modifies the store.dispatchmethod and adds other functions between the two steps of issuing Action and executing Reducer.
For example, to add log function and print out Action and State, store.dispatch can be modified as follows.

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

In the above code, store.dispatch is redefined, and printing function is added before and after sending Action. This is the prototype of middleware.

Use of middleware

For example, using redux-loggermiddleware

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

In the above code, redux-logger provides a generator createLogger, which can generate log middleware logger. Then, put it in the applyMiddleware method, pass in the createStore method, and complete the function enhancement of store.dispatch().

Pay attention to the order of middleware parameters passed in by applyMiddleware

applyMiddlewares () method

Source code:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

In the above code, all middlewares are put into an array chain, then nested and executed, and finally store.dispatch is executed. As you can see, the two methods getState and dispatch can be obtained inside the middleware (middlewareAPI).

Asynchronous operation

Synchronous operations only need to issue one Action
. There are three types of Actions for asynchronous operations: Action when initiated, Action when successful, and Action when failed. For example,

// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

Correspondingly, State also needs to be transformed, such as

let state = {
  // ...
  isFetching: true,
  didInvalidate: true,
  lastUpdated: 'xxxxxxx'
};
  1. When the operation starts, send an Action, trigger the State to update to the "operating" state, and the View is re-rendered;
  2. After the operation is over, send another Action, trigger the State to update to the "operation end" state, and the View will be re-rendered again

redux-thunk middleware

Define an Action Creator that returns a function, and then use redux-thunk middleware to transform store.dispatch

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
);

First issue an Action (requestPosts(postTitle)) to indicate the start of the operation.
After the asynchronous operation ends, an Action (receivePosts(postTitle, json)) is issued to indicate the end of the operation.

redux-promise middleware

Method 1:
Action Creator returns a Promise object

import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(promiseMiddleware)
);

//action createor
const fetchPosts =
  (dispatch, postTitle) => new Promise(function (resolve, reject) {
     dispatch(requestPosts(postTitle));
     return fetch(`/some/API/${postTitle}.json`)
       .then(response => {
         type: 'FETCH_POSTS',
         payload: response.json()
       });
});

//使用
store.dispatch(fetchPosts(store.dispatch,'reactjs'));

Method 2: The payload property of the Action object is a Promise object.
This needs to introduce the createAction method from the redux-actions module, and the writing method should also be as follows

import { createAction } from 'redux-actions';
class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 发出同步 Action
    dispatch(requestPosts(selectedPost));
    // 发出异步 Action
    dispatch(createAction(
      'FETCH_POSTS',
      fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
    ));
  }

The source code of redux-promise:

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }

    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}

As can be seen from the above code, if the Action itself is a Promise,
its value after resolve should be an Action object, which will be sent by the dispatch method (action.then(dispatch)),
but there will be no action after reject;
if the Action The payload property of the object is a Promise object, so regardless of resolve and reject, the dispatch method will issue an Action.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325226046&siteId=291194637