03.Redux 进阶

Redux 进阶

6.1 UI 组件和容器组件

UI 组件负责渲染,容器组件负责页面逻辑。

UI 只负责页面的组件 (傻瓜组件)。

容器组件里的代码都是业务逻辑代码 (聪明组件)。

示例:

// TodoList.js 只负责业务逻辑
import React, { Component } from "react";
import "antd/dist/antd.css";
import store from "./store";
import {
  getInputChangeAction,
  getBtnClickAction,
  getItemDeleteAction,
} from "./store/actionCreators";
import TodoListUI from "./TodoListUI.jsx"

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);
    this.handleItemDelete = this.handleItemDelete.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 (
      <TodoListUI inputValue={this.state.inputValue}
      list={this.state.list} 
      handleInputChange={this.handleInputChange}
      handleBtnClick={this.handleBtnClick}
      handleItemDelete={this.handleItemDelete}
      />
    );
  }
}
// TosoListUI.js 只负责渲染
import React, { Component } from 'react'
import { Button, Input, List } from "antd";

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

6.2 优化:无状态组件

里边没有 state 只有 render,就称之为无状态组件,直接用函数返回就行。如上面的 TodoListUI 组件,就是无状态组件。无状态组件的优势:性能高,就是一个函数直接返回。相比较于类组件,类组件生成的对象里面有声明周期等东西。

import React from "react";
import { Button, Input, List } from "antd";

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

6.3 Redux 中发送异步请求获取数据

比如要通过 Ajax 初始化 todo list 列表的流程:先通过挂载的周期函数发送 Ajax 请求,然后将返回的结果打包进 action 里边发送给 store,store 把旧 state 和 action 传给 reducer,reducer 判断 actionType 来进行相应的数据处理,返回新 state 给 store 进行替换。

// TodoList.js
  componentDidMount() {
    axios.get("/api/todolist").then((res) => {
      const data = res.data;
      const action = initListAction(data);
      store.dispatch(action);
    });
  }
// actionCreators.js
export const initListAction = (data) => ({
  type: INIT_LIST_ACTION,
  data,
});
// reducers.js
  if (action.type === INIT_LIST_ACTION) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;
    return newState;
  }

6.4 使用 Redux-thunk 中间件进行 ajax 请求发送

复杂的逻辑全放在组件里的时候会造成组件维护难度增加。Redux-thunk 可以解决这个问题。

  1. 安装 redux-thunk:
npm install redux-thunk
  1. 引用 applyMiddleware,使得可以构建和使用中间件。thunk 和 redux devtools 搭配使用。这里可以看出,redux devtools 也是一种中间件。
// store/index.js
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "./reducer";
import thunk from "redux-thunk";

const composeEnhancers =
  typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
    : compose;

const enhancer = composeEnhancers(applyMiddleware(thunk));

export default createStore(reducer, enhancer);
  1. 异步代码从组件函数里转移到 actionCreator 里边。使用 redux-thunk 以后,action 可以返回一个函数而不仅仅是一个对象。action 函数 dispatch 给 store 后,store 发现是个函数,该函数便可即时运行。同时,函数从生命周期中抽离,方便自动化测试。
// TodoList.js
componentDidMount() {
    store.dispatch(getTodoList());
}
// actionCreator.js
export const initListAction = (data) => ({
    type: INIT_LIST_ACTION,
    data,
});

export const getTodoList = () => { 
    // 当 action 为函数的时候,可以接收到 dispatch 方法
    return (dispatch) => { 
        // 异步发送初始化列表的 action
        axios.get("/api/todolist").then((res) => {
            const data = res.data;
            // 获取 action
            const action = initListAction(data);
            // 发送给 store
            dispatch(action);
        });
    }
}
// reducer.js
if (action.type === INIT_LIST_ACTION) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;
    return newState;
}

6.5 什么是 Redux 中间件

Redux 中间件在 Action 和 Store 之间。

中间件就是对 Dispatch 做了一层封装。

6.6 Redux-saga 中间件入门

thunk 已经很好用了,但是 Redux-saga 做了进一步的划分,将异步方法放在单独一个文件里。

  1. 安装 redux-saga

    npm install redux-saga
    
  2. 创建中间件并挂载在 store 上,并创建 sagas.js,里边放异步的方法

    import { createStore, compose, applyMiddleware } from "redux";
    import reducer from "./reducer";
    import createSagaMiddleware from "redux-saga";
    
    // create the saga middleware
    const sagaMiddleware = createSagaMiddleware();
    
    const composeEnhancers =
      typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
        ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
        : compose;
    
    const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
    
    // mount it on the Store
    export default createStore(reducer, enhancer);
    
  3. 正常方式写 action

    // TodoList.js
    componentDidMount() {
        store.dispatch(getInitList());
    }
    
    // actionCreators.js
    export const getInitList = () => ({
        type: GET_INIT_LIST,
    });
    
  4. 在 sagas 里边写异步方法

    运行原理:当 action dispatch 的时候,不仅仅 reducer 能获取到 action,saga 里的函数也能接受到,根据不同的 action 来运行不同的函数。

    import { takeEvery, put } from "redux-saga/effects";
    import { GET_INIT_LIST } from "./actionTypes";
    import { initListAction } from "./actionCreators";
    import axios from "axios";
    
    // 需要了解迭代器和生成器
    function* getInitList() {
      try {
        const res = yield axios.get("/api/todolist");
        const action = initListAction(res.data);
        yield put(action);
      } catch (error) {
        console.log("list.json 网络请求失败");
      }
    }
    
    // generator 函数
    function* mySaga() {
      // 只要接收到了 GET_INIT_LIST,就会触发 getInitList 这个方法
      yield takeEvery(GET_INIT_LIST, getInitList);
    }
    
    export default mySaga;
    

6.7 React-redux 的使用

更方便地在 React 中使用 redux

  1. 安装 react-redux

    npm install react-redux
    
  2. index.js 里引入 Provider 并包裹组件标签,并引入 store。Provider 作用:提供 store 给子组件后,子组件都能获得 store 里的内容。

    import React from "react";
    import ReactDOM from "react-dom";
    import reportWebVitals from "./reportWebVitals";
    import TodoList from "./TodoList";
    import { Provider } from "react-redux";
    import store from "./store"
    
    ReactDOM.render(
      <Provider store={store}>
        <TodoList />
      </Provider>,
      document.getElementById("root")
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
  3. 使用 connect 方法获取 store 里的数据

    // TodoList.jsx
    
    // TodoList 和 store 做连接
    export default connect(null, null)(TodoList);
    
  4. 编写映射规则1:实现 store 里的数据和 this.state 的映射

    class TodoList extends Component {
      render() {
        return (
          <div>
            <div>
              <input type="text" value={this.props.inputValue} />
              <button>提交</button>
            </div>
            <ul>
              <li>item1</li>
            </ul>
          </div>
        );
      }
    }
    
    // store 变成组件里的 props 的映射规则
    const mapStateToProps = (state) => {
      return {
        // 组件里的 props.inputValue 映射到 store 里的 inputValue
        inputValue: state.inputValue,
      };
    }
    
    // TodoList 和 store 做连接
    export default connect(mapStateToProps, null)(TodoList);
    
  5. 编写映射规则2:实现 store 里 dispatch 和 props.方法的映射

    import React, { Component } from "react";
    import { connect } from "react-redux";
    
    class TodoList extends Component {
      render() {
        return (
          <div>
            <div>
              <input
                type="text"
                value={this.props.inputValue}
                onChange={this.props.changeInputValue}
              />
              <button>提交</button>
            </div>
            <ul>
              <li>item1</li>
            </ul>
          </div>
        );
      }
    }
    
    // store 里存储的 state 变成组件里的 props 的规则
    const mapStateToProps = (state) => {
      return {
        // 组件里的 props.inputValue 映射到 store 里的 inputValue
        inputValue: state.inputValue,
      };
    };
    
    // store.dispatch 映射到 props 的方法上
    const mapDispatchToProps = (dispatch) => {
      return {
        changeInputValue(e) {
          const action = {
            type: "change_input_value",
            value: e.target.value,
          };
          dispatch(action);
        },
      };
    };
    
    // TodoList 和 store 做连接
    export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
    
  6. 在 reducer 里接收传递过来的 action,进行相应的处理

    const defaultState = {
      inputValue: "",
      list: [],
    };
    
    const reducer = (state = defaultState, action) => {
      if (action.type === "change_input_value") {
        const newState = JSON.parse(JSON.stringify(state));
        newState.inputValue = action.value;
        return newState;
      }
      return state;
    };
    
    export default reducer;
    

    至此,输入框便可顺利地通过键盘输入。

6.8 使用 React-redux 完成 TodoList 功能

6.8.1 添加提交功能
// TodoList.jsx
import React, { Component } from "react";
import { connect } from "react-redux";

class TodoList extends Component {
  render() {
    return (
      <div>
        <div>
          <input
            type="text"
            value={this.props.inputValue}
            onChange={this.props.changeInputValue}
          />
          <button onClick={this.props.handleClick}>提交</button>
        </div>
        <ul>
          {/* 渲染列表 */}
          {this.props.list.map((item, index) => {
            return <li key={index}>{item}</li>;
          })}
        </ul>
      </div>
    );
  }
}

// store 里存储的 state 变成组件里的 props 的规则
const mapStateToProps = (state) => {
  return {
    // 组件里的 props.inputValue 映射到 store 里的 inputValue
    inputValue: state.inputValue,
    // 获取 store 里的 list
    list: state.list,
  };
};

// store.dispatch 映射到 props 的方法上
const mapDispatchToProps = (dispatch) => {
  return {
    ...
    // 添加 item 操作
    handleClick() {
      const action = {
        type: "add_item",
      };
      dispatch(action);
    },
  };
};

// TodoList 和 store 做连接
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
// reducer.js
...

const reducer = (state = defaultState, action) => {
  ...
  if (action.type === "add_item") {
    const newState = JSON.parse(JSON.stringify(state));
    // 输入框的值添加到列表
    newState.list.push(newState.inputValue);
    // 清空输入框内容
    newState.inputValue = "";
    return newState;
  }
  return state;
};

export default reducer;
6.8.2 点击删除功能实现
import React, { Component } from "react";
import { connect } from "react-redux";

class TodoList extends Component {
  render() {
    return (
      <div>
        <div>
          <input
            type="text"
            value={this.props.inputValue}
            onChange={this.props.changeInputValue}
          />
          <button onClick={this.props.handleClick}>提交</button>
        </div>
        <ul>
          {/* 渲染列表 */}
          {this.props.list.map((item, index) => {
            return (
              <li
                key={index}
                onClick={(index) => this.props.handleDelete(index)}
              >
                {item}
              </li>
            );
          })}
        </ul>
      </div>
    );
  }
}

// store 里存储的 state 变成组件里的 props 的规则
const mapStateToProps = (state) => {
  return {
    // 组件里的 props.inputValue 映射到 store 里的 inputValue
    inputValue: state.inputValue,
    // 获取 store 里的 list
    list: state.list,
  };
};

// store.dispatch 映射到 props 的方法上
const mapDispatchToProps = (dispatch) => {
  return {
    // 输入的值存储到 store
    changeInputValue(e) {
      const action = {
        type: "change_input_value",
        value: e.target.value,
      };
      dispatch(action);
    },
    // 添加 item 操作
    handleClick() {
      const action = {
        type: "add_item",
      };
      dispatch(action);
    },
    // 点击 item 删除 item
    handleDelete(index) {
      const action = {
        type: "delete_item",
        index,
      };
      dispatch(action);
    },
  };
};

// TodoList 和 store 做连接
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
const defaultState = {
  inputValue: "",
  list: [],
};

const reducer = (state = defaultState, action) => {
  if (action.type === "change_input_value") {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === "add_item") {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = "";
    return newState;
  }
  if (action.type === "delete_item") {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
};

export default reducer;

TodoList 的基本功能到此全部实现。可以看到 react-redux 的优点在于,组件使用了 connect 的方法进行映射,使得在 store 里的数据发生改变的时候,组件里的获取数据便可跟着改变,而不需要以前 subscribe 来获取数据。

connect 的理解:

// TodoList 和 store 做连接
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

可以看到,TodoList 组件文件导出的并非组件自身,而是 connect 方法运行返回的结果,TodoList 只是 UI 组件,负责显示,connect 把 UI 组件和其他业务逻辑相结合,便组合成了容器组件。

6.8.2 后边需要做的事。。。

actionCreator,actionTypes 等等优化代码可维护性。

使用解构赋值解构 this.state 和 this.props 可以使得代码更优雅。

TodoList 可以写成无状态组件(因为并没有 state,都是从 store 里获取的 state)

完整代码:

// TodoList.jsx
import React from "react";
import { connect } from "react-redux";
import {
  getAddItemAction,
  getChangeInputValueAction,
  getDeleteAction,
} from "./store/actionCreator";

const TodoList = (props) => {
  const { inputValue, list, changeInputValue, handleClick, handleDelete } =
    props;
  return (
    <div>
      <div>
        <input type="text" value={inputValue} onChange={changeInputValue} />
        <button onClick={handleClick}>提交</button>
      </div>
      <ul>
        {/* 渲染列表 */}
        {list.map((item, index) => {
          return (
            <li key={index} onClick={(index) => handleDelete(index)}>
              {item}
            </li>
          );
        })}
      </ul>
    </div>
  );
};

// store 里存储的 state 变成组件里的 props 的规则
const mapStateToProps = (state) => {
  return {
    // 组件里的 props.inputValue 映射到 store 里的 inputValue
    inputValue: state.inputValue,
    // 获取 store 里的 list
    list: state.list,
  };
};

// store.dispatch 映射到 props 的方法上
const mapDispatchToProps = (dispatch) => {
  return {
    // 输入的值存储到 store
    changeInputValue(e) {
      dispatch(getChangeInputValueAction(e.target.value));
    },
    // 添加 item 操作
    handleClick() {
      dispatch(getAddItemAction());
    },
    // 点击 item 删除 item
    handleDelete(index) {
      dispatch(getDeleteAction(index));
    },
  };
};

// TodoList 和 store 做连接
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
// actionCreator.js
import { ADD_ITEM, CHANGE_INPUT_VALUE, DELETE_ITEM } from "./actionTypes";

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

export const getAddItemAction = () => ({
  type: ADD_ITEM,
});

export const getDeleteAction = (index) => ({
  type: DELETE_ITEM,
  index,
});
// reducer.js
import { ADD_ITEM, CHANGE_INPUT_VALUE, DELETE_ITEM } from "./actionTypes";

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

const reducer = (state = defaultState, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === ADD_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = "";
    return newState;
  }
  if (action.type === DELETE_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
};

export default reducer;
// actionTypes.js
export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_ITEM = "add_item";
export const DELETE_ITEM = "delete_item";
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值