02.Redux 入门

Redux 入门

5.1 Redux 概念简述

单单靠组件间传值太麻烦了,因此 Redux 应运而生。Redux 是数据层框架,把数据放在 store 这个共用存储空间里。其他组件取数据也在 store 里取。

Redux = Reducer + Flux

image-20220315203000405

5.2 Redux 的工作流程

下面一个形象的比喻来解释 Redux Flow 工作流程:图书馆借书

React Component: 借书的用户

Action Creators: 借书所说的话

Store: 图书馆管理员

Reducers: 管理员的记录本

转化成代码理解:

组件要获取 Store 里的数据,Action Creator 创建语句后告诉 Store,Store 接收到了但是不知道要什么数据,所以传给了 Reducer,然后 Reducer 告诉 Store 应该给哪些数据。

5.3 实战:使用 antd 编写 TodoList 页面布局

Ant Design of React 是一个基于 React 的 UI 库,可以快速搭建有着漂亮样式的前端页面。

import React, { Component } from "react";
// 引入样式
import "antd/dist/antd.css";
// 引入组件
import { Button, Input, List } from "antd";

export default class TodoList extends Component {
  render() {
    const data = [
      "Racing car sprays burning fuel into crowd.",
      "Japanese princess to wed commoner.",
      "Australian walks 100km after outback crash.",
      "Man charged over missing wedding girl.",
      "Los Angeles battles huge wildfires.",
    ];

    return (
      <div style={{ marginTop: "10px", marginLeft: "10px" }}>
        <Input
          placeholder="todo info"
          style={{ width: "300px", marginRight: "10px" }}
        />
        <Button type="primary">提交</Button>
        <List
          style={{ marginTop: "10px", width: "300px" }}
          bordered
          dataSource={data}
          renderItem={(item) => <List.Item>{item}</List.Item>}
        />
      </div>
    );
  }
}

效果图:

5.4 Store 的创建

安装 Redux 包后,在 src 目录下创建 store 文件夹,并添加 index.js 文件。

  1. 创建 store:

    import { createStore } from "redux";
    
    export const store = createStore();
    
  2. 同时创建一个文件,名字叫 reducer.js

    // 有两个默认数据
    const defaultState = {
      inputValue: "hhhh",
      list: [1, 2],
    };
    
    // state 为 store 仓库里存储的数据
    const reducer = (state = defaultState, action) => {
      return state;
    }
    
    export default reducer;    
    
  3. 把数据传递给 Store

    import { createStore } from "redux";
    import reducer from "./reducer";
    
    // reducer 让 store 知道了存储的数据
    export default createStore(reducer);
    
  4. 组件从 store 里获取数据

    ...
    import store from "./store";
    
    export default class TodoList extends Component {
      constructor(props) {
        super(props);
        // 使用这个方法便可获取存储的 State
        this.state = store.getState();
      }
      render() {
        return (
          <div style={{ marginTop: "10px", marginLeft: "10px" }}>
            <Input
              value={this.state.inputValue}
              placeholder="todo info"
              style={{ width: "300px", marginRight: "10px" }}
            />
            <Button type="primary">提交</Button>
            <List
              style={{ marginTop: "10px", width: "300px" }}
              bordered
              dataSource={this.state.list}
              renderItem={(item) => <List.Item>{item}</List.Item>}
            />
          </div>
        );
      }
    }
    
  5. 显示效果

5.5 Action 和 Reducer 的编写

目标:实现功能,当 input 框内容发生改变的时候,Store 里的数据也跟着改变。

  1. 先安装 chrome 插件 redux -dev-tools

  2. 在 Store 里添加配置

    import { createStore } from "redux";
    import reducer from "./reducer";
    
    export default createStore(
      reducer,
      window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );
    
  3. 打开开发者工具,一目了然

  4. Input 绑定事件

    <Input
    // 输入结果绑定
    value={this.state.inputValue}
    placeholder="todo info"
    style={{ width: "300px", marginRight: "10px" }}
    // 绑定事件
    onChange={this.handleInputChange}
    />
    
  5. 事件代码如下,成功把语句传输给 Store

    handleInputChange(e) {
        // 声明 action 语句
        const action = {
            type: "change_input_value",
            value: e.target.value,
        };
        // action 传给 store
        store.dispatch(action);
    }
    
  6. Store 把之前存储的 state 和 action 转发给 Reducers

    // reducers.js
    const reducer = (state = defaultState, action) => {
      console.log(state, action);
      return state;
    };
    

  7. Reducers 返回新的 state 数据给 Store。Store 接收到 newState 后,原本的 state 被替换

    // reducers.js
    const defaultState = {
      inputValue: "hhhh",
      list: [1, 2],
    };
    
    // Reducer 可以接受 state,但是决不能修改 state
    const reducer = (state = defaultState, action) => {
      if (action.type === "change_input_value") {
        // 对之前的 state 进行深拷贝
        const newState = JSON.parse(JSON.stringify(state));
        // 把 state 的数据改成新的值
        newState.inputValue = action.value;
        return newState;
      }
      return state;
    };
    
    export default reducer;
    
  8. 虽然 Store 里的 state 改变了,但是组件的数据并没有更新,因为还需要组件订阅 Store 的步骤。

    订阅后,用订阅触发的方法来获取新数据即可。

    constructor(props) {
        super(props);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        // 该方法订阅了 store 的改变。store 改变一次,该方法将会执行一次。
        store.subscribe(this.handleStoreChange);
    }
    
    handleStoreChange() {
        // 用 store 里获取的数据来替换原本的数据
        this.setState(store.getState());
    }
    

  9. 继续实现添加按钮添加 item 的方法

    // TodoList.js
    import React, { Component } from "react";
    import "antd/dist/antd.css";
    import { Button, Input, List } from "antd";
    import store from "./store";
    
    export default class TodoList extends Component {
      constructor(props) {
        super(props);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        // 订阅了 store 的改变。store 改变一次,该方法将会执行一次。
        store.subscribe(this.handleStoreChange);
      }
        
      handleStoreChange() {
        // 用 store 里获取的数据来替换原本的数据
        this.setState(store.getState());
      }
    
      handleInputChange(e) {
        // 声明 action 语句
        const action = {
          type: "change_input_value",
          value: e.target.value,
        };
        // action 传给 store
        store.dispatch(action);
      }
    
      handleBtnClick() {
        const action = {
          type: "add_todo_item",
        };
        store.dispatch(action);
      }
    
      render() {
        return (
          <div style={{ marginTop: "10px", marginLeft: "10px" }}>
            <Input
              value={this.state.inputValue}
              placeholder="todo info"
              style={{ width: "300px", marginRight: "10px" }}
              onChange={this.handleInputChange}
            />
            <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
            <List
              style={{ marginTop: "10px", width: "300px" }}
              bordered
              dataSource={this.state.list}
              renderItem={(item) => <List.Item>{item}</List.Item>}
            />
          </div>
        );
      }
    }
    
    // reducer.js
    const defaultState = {
      inputValue: "",
      list: [],
    };
    
    // Reducer 可以接受 state,但是决不能修改 state
    const reducer = (state = defaultState, action) => {
      if (action.type === "change_input_value") {
        // 对之前的 state 进行深拷贝
        const newState = JSON.parse(JSON.stringify(state));
        // 把 state 的数据改成新的值
        newState.inputValue = action.value;
        return newState;
      }
      if (action.type === "add_todo_item") {
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        newState.inputValue = "";
        return newState;
      }
      return state;
    };
    
    export default reducer;
    

5.6 使用 Redux 完成 TodoList 删除功能

// method
handleItemDelete(index) {
    const action = {
        type: "delete_todo_item",
        index
    }
    store.dispatch(action);
}

// render 
<List
style={{ marginTop: "10px", width: "300px" }}
bordered
dataSource={this.state.list}
renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>}
/>
    
// reducer
if (action.type === "delete_todo_item") {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
}

5.7 ActionTypes 的拆分

上面的方法使用的时候肯定发现了有一个问题,type 是长字符串,一旦拼写出现一丁点错误,则 redux 功能就不好使。

因此 ActionTypes 应运而生。

store 文件夹里新建 actionTypes.js 文件,声明一系列的 actions 名称

// actionTypes.js
export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_TODO_ITEM = "add_todo_item";
export const DELETE_TODO_ITEM = "delete_todo_item";

后边其他文件需要用的话,直接导入即可

// TodoList.js && reducer.js
import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM,
} from "./actionTypes";

5.8 使用 actionCreator 统一创建 action

上面的 action 的创建方法比较简陋,如果 action 太多,将会到处都是,不方便管理。因此需要 actionCreators 统一创建和管理 action,提高代码的可维护性。

修改前
handleInputChange(e) {
    const action = {
        type: CHANGE_INPUT_VALUE,
        value: e.target.value,
    };
    store.dispatch(action);
}
优化

创建 actionCreators.js 并在里边定义 action

// actionCreators.js 
import { CHANGE_INPUT_VALUE } from "./actionTypes";

// 返回 action
export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value,
});

在组件代码里修改后:

handleInputChange(e) {
    store.dispatch(getInputChangeAction(e.target.value));
}
完整代码

TodoList.js

import React, { Component } from "react";
import "antd/dist/antd.css";
import { Button, Input, List } from "antd";
import store from "./store";
import {
  getInputChangeAction,
  getBtnClickAction,
  getItemDeleteAction,
} from "./store/actionCreators";

export default class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleStoreChange = this.handleStoreChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    // 订阅了 store 的改变。store 改变一次,该方法将会执行一次。
    store.subscribe(this.handleStoreChange);
  }

  handleStoreChange() {
    // 用 store 里获取的数据来替换原本的数据
    this.setState(store.getState());
  }

  handleInputChange(e) {
    store.dispatch(getInputChangeAction(e.target.value));
  }

  handleBtnClick() {
    store.dispatch(getBtnClickAction());
  }

  handleItemDelete(index) {
    store.dispatch(getItemDeleteAction(index));
  }

  render() {
    return (
      <div style={{ marginTop: "10px", marginLeft: "10px" }}>
        <Input
          value={this.state.inputValue}
          placeholder="todo info"
          style={{ width: "300px", marginRight: "10px" }}
          onChange={this.handleInputChange}
        />
        <Button type="primary" onClick={this.handleBtnClick}>
          提交
        </Button>
        <List
          style={{ marginTop: "10px", width: "300px" }}
          bordered
          dataSource={this.state.list}
          renderItem={(item, index) => (
            <List.Item onClick={this.handleItemDelete.bind(this, index)}>
              {item}
            </List.Item>
          )}
        />
      </div>
    );
  }
}

reducer.js

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM,
} from "./actionTypes";

const defaultState = {
  inputValue: "",
  list: [],
};

// Reducer 可以接受 state,但是决不能修改 state
const reducer = (state = defaultState, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    // 对之前的 state 进行深拷贝
    const newState = JSON.parse(JSON.stringify(state));
    // 把 state 的数据改成新的值
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === ADD_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = "";
    return newState;
  }
  if (action.type === DELETE_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
};

export default reducer;

actionCreator.js

import { ADD_TODO_ITEM, CHANGE_INPUT_VALUE, DELETE_TODO_ITEM } from "./actionTypes";

export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value,
});

export const getBtnClickAction = () => ({
  type: ADD_TODO_ITEM
})

export const getItemDeleteAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index,
})

actionTypes.js

export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_TODO_ITEM = "add_todo_item";
export const DELETE_TODO_ITEM = "delete_todo_item";

store/index.js

import { createStore } from "redux";
import reducer from "./reducer";

export default createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

5.9 Redux 知识点复习补充

Redux 设计和使用的三项原则
  1. store 是唯一的。
  2. 只有 store 能够改变自己的内容。store 里的数据不是 reducer 更新的,而是拿到 reducer 返回的数据,自己对自己的内容更新。因此 reducer 里改变 state 需要先深拷贝。
  3. reducer 必须是个纯函数。纯函数指的是,指定固定的输入,就一定会有固定的输出,且不会有任何副作用。另外一种话来讲,输入给定的时候输出固定永恒不变,且不对输入参数进行修改(副作用),即为纯函数。

常用 API

  1. createStore
  2. store.dispatch
  3. store.getState
  4. store.subscribe
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值