Redux(ToolKit Version)学习笔记 (勉強ノート)

3 篇文章 0 订阅

Redux官网

应用于React

When should I use Redux?

In general, use Redux when you have reasonable amounts of data changing over time, you need a single source of truth, and you find that approaches like keeping everything in a top-level React component’s state are no longer sufficient.

However, it’s also important to understand that using Redux comes with tradeoffs. It’s not designed to be the shortest or fastest way to write code. It’s intended to help answer the question “When did a certain slice of state change, and where did the data come from?”, with predictable behavior. It does so by asking you to follow specific constraints in your application: store your application’s state as plain data, describe changes as plain objects, and handle those changes with pure functions that apply updates immutably. This is often the source of complaints about “boilerplate”. These constraints require effort on the part of a developer, but also open up a number of additional possibilities (such as store persistence and synchronization).
解决编写代码模板化问题 可以自行编辑模板代码(使用VSCode IDE)
详见
Vscode创建自定义代码模板(eg:xxx.jsx)[javascriptreact.json]
最最重要的一个问题 保持数据的唯一性以及同步性
个人理解为 良好的状态管理 避免脏读

What is Redux

Redux is a pattern and library for managing and updating application state, using events called “actions”. It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.
用store集中管控需要跨级的state

Why Should I Use Redux

Redux helps you manage “global” state - state that is needed across many parts of your application.
Redux guides you towards writing code that is predictable and testable, which helps give you confidence that your application will work as expected.

集中管理实现数据可控
可用于控件解耦(消除props) 管控后台api数据

React-Redux

Redux can integrate with any UI framework, and is most frequently used with React. React-Redux is our official package that lets your React components interact with a Redux store by reading pieces of state and dispatching actions to update the store.
官方包 建立React与Redux的关系
eg:
import { useDispatch, useSelector } from “react-redux”;

Terminology

Action

brief description
An object describing what happened, and dispatch it to the store.

An action is a plain JavaScript object that has a type field. You can think of an action as an event that describes something that happened in the application.

The type field should be a string that gives this action a descriptive name, like “todos/todoAdded”. We usually write that type string like “domain/eventName”, where the first part is the feature or category that this action belongs to, and the second part is the specific thing that happened.

An action object can have other fields with additional information about what happened. By convention, we put that information in a field called payload.

A typical action object might look like this:

const addTodoAction = {
  type: "todos/todoAdded",
  payload: "Buy milk",
};

Action Creators

An action creator is a function that creates and returns an action object. We typically use these so we don’t have to write the action object by hand every time:

const addTodo = (text) => {
  return {
    type: "todos/todoAdded",
    payload: text,
  };
};

返回action object的function

Reducer

brief description
A function that takes a current state value and an action object describing “what happened”, and returns a new state value.
A reducer’s function signature is: (state, action) => newState

You can think of a reducer as an event listener which handles events based on the received action (event) type.

Reducers must always follow some specific rules:

  • They should only calculate the new state value based on the state and
    action arguments

  • They are not allowed to modify the existing state. Instead, they must
    make immutable updates, by copying the existing state and making
    changes to the copied values.
    只能修改copied states

  • They must not do any asynchronous logic, calculate random values, or
    cause other “side effects”

The logic inside reducer functions typically follows the same series of steps:

  1. Check to see if the reducer cares about this action
    If so, make a copy of the state, update the copy with new values, and return it

  2. Otherwise, return the existing state unchanged

small example of a reducer

const initialState = { value: 0 };

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === "counter/increment") {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1,
    };
  }
  // otherwise return the existing state unchanged
  return state;
}

Reducers can use any kind of logic inside to decide what the new state should be: if/else, switch, loops, and so on.
不关心计算过程 随便算

Store

The current Redux application state lives in an object called the store
The store is created by passing in a reducer, and has a method called getState that returns the current state value:

import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({ reducer: counterReducer });

console.log(store.getState());
// {value: 0}

Dispatch

brief description
The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object. The store will run its reducer function and save the new state value inside, and we can call getState() to retrieve the updated value:
dispatch是Redux中的方法

store.dispatch({ type: "counter/increment" });

console.log(store.getState());

You can think of dispatching actions as “triggering an event” in the application. Something happened, and we want the store to know about it. Reducers act like event listeners, and when they hear an action they are interested in, they update the state in response.
事件触发器

const increment = () => {
  return {
    type: "counter/increment",
  };
};

store.dispatch(increment());

console.log(store.getState());

Selectors

Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data:

const selectCounterValue = (state) => state.value;

const currentValue = selectCounterValue(store.getState());
console.log(currentValue);

获取strore中当前的state

Simple Example

Basic Example

import { createStore } from "redux";

/**
 * This is a reducer - a function that takes a current state value and an
 * action object describing "what happened", and returns a new state value.
 * A reducer's function signature is: (state, action) => newState
 *
 * The Redux state should contain only plain JS objects, arrays, and primitives.
 * The root state value is usually an object.  It's important that you should
 * not mutate the state object, but return a new object if the state changes.
 *
 * You can use any conditional logic you want in a reducer. In this example,
 * we use a switch statement, but it's not required.
 */
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case "counter/incremented":
      return { value: state.value + 1 };
    case "counter/decremented":
      return { value: state.value - 1 };
    default:
      return state;
  }
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counterReducer);

// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// There may be additional use cases where it's helpful to subscribe as well.

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

// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: "counter/incremented" });
// {value: 1}
store.dispatch({ type: "counter/incremented" });
// {value: 2}
store.dispatch({ type: "counter/decremented" });
// {value: 1}

Redux Toolkit Example

import { createSlice, configureStore } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: {
    value: 0,
  },
  reducers: {
    incremented: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decremented: (state) => {
      state.value -= 1;
    },
  },
});

export const { incremented, decremented } = counterSlice.actions;

const store = configureStore({
  reducer: counterSlice.reducer,
});

// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()));

// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented());
// {value: 1}
store.dispatch(incremented());
// {value: 2}
store.dispatch(decremented());
// {value: 1}

one-way data flow

  • State describes the condition of the app at a specific point in time
  • The UI is rendered based on that state
  • When something happens (such as a user clicking a button), the state
    is updated based on what occurred
  • The UI re-renders based on the new state

正常的UI渲染流程:
state驱动UI刷新
state代表应用在当前时间的某个状态
state因为某些动作改变为new state
UI根据这种改变而刷新
one-way data flow

应用Redux时的UI渲染流程:
Redux 初始化:
根据root reducer创建store(可根据RTK的combineReducers function将所有模块的reducer 进行整合 绑定为一个对象【root reducer】)
store call 一次root reducer 将返回值设置为初始state
UI初次渲染时使用初始state刷新

Redux更新:
UI有动作产生时(例如用户操作)
dispatch action to store
dispatch派发action到store
store根据上一次的存储的state和当前的action去跑对应的reducer方法 从而产生新的state (new state)
store通知所有UI与其相关的部分 store已经被更新
对应的从store中获取了数据的控件检查state是否被更新
刷新UI 将new state反应至显示媒介上
在这里插入图片描述

The Counter Example App(JS Version)

Application Contents
在这里插入图片描述
Creating the Redux Store
app/store.js

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
});

For example, in a blogging app, our store setup might look like:
多个redecuer通过combineReducers方法进行绑定

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import usersReducer from "../features/users/usersSlice";
import postsReducer from "../features/posts/postsSlice";
import commentsReducer from "../features/comments/commentsSlice";

const rootReducer = combineReducers({
  users: usersReducer,
  posts: postsReducer,
  comments: commentsReducer,
});

export default configureStore({
  reducer: rootReducer,
});

Redux Slices
A “slice” is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file. The name comes from splitting up the root Redux state object into multiple “slices” of state.
对应模块的redecer和action的切片

Creating Slice Reducers and Actions
eatures/counter/counterSlice.js

import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
  name: "counter",
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

The “counter” name + the “increment” reducer function generated an action type of {type: “counter/increment”}.
createSlice automatically generates action creators with the same names as the reducer functions we wrote. We can check that by calling one of them and seeing what it returns:

console.log(counterSlice.actions.increment());
// {type: "counter/increment"}

Reducers and Immutable Updates
In Redux, our reducers are never allowed to mutate the original / current state values!
在Redux 永远不允许reducer修改元数据

Reducers can only make copies of the original values, and then they can mutate the copies.
createSlice uses a library called Immer inside. Immer uses a special JS tool called a Proxy to wrap the data you provide, and lets you write code that “mutates” that wrapped data. But, Immer tracks all the changes you’ve tried to make, and then uses that list of changes to return a safely immutably updated value, as if you’d written all the immutable update logic by hand.

Writing Async Logic with Thunks

A thunk is a specific kind of Redux function that can contain asynchronous logic. Thunks are written using two functions:

  • An inside thunk function, which gets dispatch and getState as arguments
  • The outside creator function, which creates and returns the thunk function

The next function that’s exported from counterSlice is an example of a thunk action creator:

// The function below is called a thunk and allows us to perform async logic.
// It can be dispatched like a regular action: `dispatch(incrementAsync(10))`.
// This will call the thunk with the `dispatch` function as the first argument.
// Async code can then be executed and other actions can be dispatched
export const incrementAsync = (amount) => (dispatch) => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount));
  }, 1000);
};

However, using thunks requires that the redux-thunk middleware (a type of plugin for Redux) be added to the Redux store when it’s created.
Redux Toolkit’s configureStore function already sets that up for us automatically.

When you need to make AJAX calls to fetch data from the server, you can put that call in a thunk. Here’s an example that’s written a bit longer, so you can see how it’s defined:

// the outside "thunk creator" function
const fetchUserById = (userId) => {
  // the inside "thunk function"
  return async (dispatch, getState) => {
    try {
      // make an async call in the thunk
      const user = await userAPI.fetchById(userId);
      // dispatch an action when we get the response back
      dispatch(userLoaded(user));
    } catch (err) {
      // If something went wrong, handle it here
    }
  };
};

Redux Thunk middleware
The Redux Thunk middleware modifies the store to let you pass functions into dispatch. In fact, it’s short enough we can paste it here:

const thunkMiddleware = ({ dispatch, getState }) => (next) => (action) => {
  if (typeof action === "function") {
    return action(dispatch, getState, extraArgument);
  }

  return next(action);
};

It looks to see if the “action” that was passed into dispatch is actually a function instead of a plain action object. If it’s actually a function, it calls the function, and returns the result. Otherwise, since this must be an action object, it passes the action forward to the store.

This gives us a way to write whatever sync or async code we want, while still having access to dispatch and getState.

Reading Data with useSelector
First, the useSelector hook lets our component extract whatever pieces of data it needs from the Redux store state.

Earlier, we saw that we can write “selector” functions, which take state as an argument and return some part of the state value.

features/counter/counterSlice.js

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
export const selectCount = (state) => state.counter.value;

Our components can’t talk to the Redux store directly, because we’re not allowed to import it into component files. But, useSelector takes care of talking to the Redux store behind the scenes for us. If we pass in a selector function, it calls someSelector(store.getState()) for us, and returns the result.

So, we can get the current store counter value by doing:

const count = useSelector(selectCount);

Any time an action has been dispatched and the Redux store has been updated, useSelector will re-run our selector function. If the selector returns a different value than last time, useSelector will make sure our component re-renders with the new value.
一旦store被更新(action被dispatch)useSelector 就会重跑selector function
只要selector返回的值跟上次的值不一样(store中管理的复制的state)就会驱动UI刷新
注意useSelector的滥用问题 避免UI非必要刷新
可通过在父组件中useSelector取出数据 通过props传给子组件等方式优化

Dispatching Actions with useDispatch
Similarly, we know that if we had access to a Redux store, we could dispatch actions using action creators, like store.dispatch(increment()). Since we don’t have access to the store itself, we need some way to have access to just the dispatch method.

The useDispatch hook does that for us, and gives us the actual dispatch method from the Redux store:

const dispatch = useDispatch();

Providing the Store
index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./app/store";
import { Provider } from "react-redux";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

We always have to call ReactDOM.render() to tell React to start rendering our root component. In order for our hooks like useSelector to work right, we need to use a component called to pass down the Redux store behind the scenes so they can access it.

import { Provider } from “react-redux”;

TODO
https://redux.js.org/tutorials/essentials/part-3-data-flow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值