likes
comments
collection
share

(30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

作者站长头像
站长
· 阅读数 4
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

1 什么是 React-redux

在本篇之前的系列文章中,我们分别用实例详细介绍了 React 和 Redux。用心跟下来的你,肯定已经区分出了它们的不同!如:

1 Redux 概念

通过前面文章的学习,我们算是入了 React 的门,可以做一些小的效果了。

但是,正如之前的文章所讲,React 只是一个“视图层”框架。如果我们想做一个大型应用,就需要在 React 的基础上去配套一个“数据层”的框架(如 Redux)。

只有将这两者结合着使用,我们才能更轻松地开发大型应用。

本篇我们再引入一个在实际项目中肯定会用到的一个“工具”——React-redux

之所以说它是“工具”,因为它是 Redux 的作者封装的一个“React 专用的库”。

你可以理解为 React-redux 是 Redux 包装了 React 的 Redux!因为,Redux 并不是 React 专用的,市面上“三大框架”之一的 Angular.js 也可以用 Redux。

当然,实际项目中,我们还是需要权衡一下,是直接使用 Redux,还是选用 React-Redux。React-Redux 虽然提供了很多便利且高效,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。

接下来,我们就用 React-redux 来重写一遍 Todolist 的所有功能,见识一下它的“便利”和“高效”之处。

2 取得并显示数据

在本版本的代码中,我们初次接触到 Redux,在熟悉了 Redux 基本工作流程后,运用 Antd 构建了基本的 TodoList 布局。按照“工作流程”,下一步就需要去:创建 store --> 创建 reducer --> 取得并显示数据等。

OK,React-redux 对 TodoList 的改写就从这一步开始(学习的过程中,请自行根据之前的文章比较“直接用 Redux”和“换用 React-redux”的差异点在哪里):

1️⃣首先,在项目中安装 Redux 和 React-redux: 🔗Redux 🔗React-redux

(30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

(30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

2️⃣接着,在 src 目录下创建一个“文件夹” store ,并在 store 文件夹下创建一个“文件” index.js ——即,存放 store 代码的位置: (30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

3️⃣有了存放 store 代码的位置 index.js ,我们就可以在里边用代码来创建一个数据的“公共存储仓库”:

import { createStore } from "redux"; /*
																		 3️⃣-①:从 redux 这个第三方模块中,
																		 引入 createStore 方法;
                                      */

const store = createStore(); // 3️⃣-②:调用这个方法创建一个 store;

export default store; // 3️⃣-③:将创建的 store 导出。

4️⃣❗️❗️❗️由于 store 仅仅是一个“图书管理员”,它记不住到底应该怎么去管理数据。故,它需要一个“记录本”reducer 来辅助它去管理数据。因此,我们在创建 store 的同时,必须要把这个“记录本”一并传给 store,否则 store 什么也不知道。

4️⃣-①:所以,我们还得去创建 reducer.js 文件; (30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

4️⃣-②:在 Redux 中, reducer.js 文件需要返回一个“函数”;

const defaultState = { /*
											 4️⃣-④:既然 reducer 的一个重要功能是“放置数据”,
											 那么根据之前做 TodoList 的经验,
                       TodoList 里边需要两项“默认数据”:inputVlue 和 list;
                        */

	inputValue: "Hello, Oli.", // 💡为了便于讲解,我们先给这两项数据分别都设置一个初始值。
  list: [1,2,3]
};

// 4️⃣-⑤:上边有了“默认数据”,这里记得让“参数”state 等于 defaultState;
export default (state = defaultState, action) => { /*
																		 4️⃣-③:返回的“函数”接受两个固定参数:
																		 state 和 action。
                                     state 指:整个仓库里存储的数据(可以形象地理解为,“记录本”
                                     里记录的“图书馆”中所有的书籍信息);
                                     
                                     action 指:传过来的那句话。
                                                    */
  
  return state; // 🚀默认返回 state。
}

4️⃣-⑥:同时,在前边创建 store 的时候index.js ),我们要把“记录本”reducer 传递给 store,并将 reducer 作为第一个“参数”传递给“方法” createStore()

import { createStore } from "redux"; 

import reducer from "./reducer"; // 🚀从当前目录下的 reducer.js 引入 reducer。

const store = createStore(reducer); // ❗️❗️❗️将 reducer 作为第一个“参数”传递给“方法”createStore!

export default store; 

4️⃣-⑦:既然把“记录本”reducer 传递给了 store,那么 store 就知道这个仓库里边有 inputValue 和 list 这两个数据了;

5️⃣通过以上的步骤,store 里边就有数据。按照 Redux 的工作流程,一旦有了数据,各“组件”就可以连接 store,去 store 里边取数据并显示出来。

5️⃣-①:打开 TodoList.js 文件,将里边之前“写死”的数据删除掉;

import React, {Component} from "react";
import 'antd/dist/antd.css';

import { Input, Button, List } from 'antd'; 

/*
❗️删除“写死”的数据!
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.',
];
 */

class TodoList extends Component {
  
  render() {
    return (
      <div style={{marginTop: "10px", marginLeft: "10px"}}>
        <div>
          <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>
      </div>
    )
  }
}

export default TodoList;

5️⃣-②:打开 src 目录下的 index.js 文件;

import React from 'react';
import ReactDOM from 'react-dom';

import TodoList from './TodoList';

/*
5️⃣-③:之前我们是将 Todolist 引入进来,然后在这里去直接挂载并渲染 TodoList 这个组件。
可有了 React-redux 后,这一步就不这样做了!

ReactDOM.render(<TodoList />, document.getElementById('root'));
 */

/*
❗️❗️❗️5️⃣-④:有了 React-redux 后,我们会首先从 react-redux 引入一个 Provider 组件(
也是 React-redux 的核心 API 之一);
 */
import {Provider} from "react-redux";

// 5️⃣-⑤:同时,在本文件中引入 store;
import store from "./store";


const App = ( // 5️⃣-⑥:然后,用 JSX 语法定义一个 App 组件;
  
  // 5️⃣-⑦:里边用 Provider 包裹“组件”TodoList;
  <Provider store={store}> {/*
  													❗️5️⃣-⑧:给 Provider 添加一个“属性”,
                            使其等于“5️⃣-⑤”中引入的 store。
                            这一步的意思就是:
                            “提供器 Provider”连接了 store,那么 Provider 里边的所有
                            “组件”(如这里的 TodoList)都有能力获取到 store 里的数据了!
                             */}
    <TodoList />
  </Provider>
)

/*
❗️5️⃣-⑨:最后,把 App 作为“组件”传递给 ReactDOM.render。
即,现在是挂载并渲染 App 组件了!
 */
ReactDOM.render(App, document.getElementById('root'));

6️⃣通过上边的操作后,TodoList 已经拥有了获取 store 中数据的“能力”。可光有“能力”可不行,要具体怎么去获取呢? 打开 TodoList.js 文件:

import React, {Component} from "react";
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd'; 

/*
6️⃣-①:从 react-redux 中引入 connect 方法(它也是 React-redux 的核心 API 之一),
connect 的作用很明确——就是“连接”的意思!
 */
import {connect} from "react-redux";

class TodoList extends Component {
  
  render() {
    return (
      <div style={{marginTop: "10px", marginLeft: "10px"}}>
        <div>
      
          {/* 6️⃣-⑨:将获取到的数据 inputValue 展示在页面上; */}
          <Input value={this.props.inputValue} placeholder="todo info" style={{width: "300px", marginRight: "10px"}} /> 
          
          <Button type="primary">提交</Button>  

          {/* 6️⃣-⑩:将获取到的数据 list 展示在页面上; */}
          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.props.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />

        </div>
      </div>
    )
  }
}

// 6️⃣-⑤:接下来,我们先定义“连接”的“规则”;
const mapStateToProps = (state) => { /*
																		 6️⃣-⑥:把 store 里的“数据 state”作为“参数”
                                     传递给 mapStateToProps;
                                      */
  
  return { // ❗️这个“规则”会返回一个“对象”出去;
    inputValue: state.inputValue, /*
    															6️⃣-⑦:“规则”的具体做法为——将 store 里的 inputValue
                                  映射到“TodoList 组件”里的 props 的 inputValue 中去;
                                   */
    
    list: state.list /*
    								 6️⃣-⑧:同理,将 store 里的 list
                     映射到“TodoList 组件”里的 props 的 list 中去;
                      */
  }
}


/*
6️⃣-②:之前我们直接导出的是 TodoList,可用了 React-redux 后,就不能这样写了!
export default TodoList;
 */

/*
6️⃣-③:取而代之,导出 connect 方法(
❗️注意看我们给 connect 方法传递了哪些参数!);
 */
export default connect(mapStateToProps, null)(TodoList); /*
																		6️⃣-④:我们一共给 connect 传递了 3 个参数!
                                    TodoList 表示:connect 会让“TodoList 组件”和 store 进行
                                    “连接”(由“5️⃣-⑧”可知,TodoList 已经拥有“能力”
                                    连接 store);
                                    
                                    mapStateToProps 表示:“TodoList 组件”和 
                                    store 进行“连接”是需要“规则”的,而具体的“规则”
                                    就在这个 mapStateToProps 里边(❗️直译为:把 store 
                                    里边的“数据 state”映射到 TodoList 的 props 里);
                                    
                                    null 表示:这里还会接收一个名叫 mapDispatchToProps 的
                                    参数,等下讲解,这里先用 null 占位。
                                                          */

返回页面查看效果(页面正常运行): (30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

3 改变数据

根据以往经验,“实现输入框的交互”其实就是去改变 store 中 inputValue 的值—— getInputChangeAction

❓在 React-redux 中,我们是怎样去改变这个值的呢?

上文中,我们实现了 store 和“TodoList 组件”的关联,让 store 中的“数据 state”映射到了 TodoList 的 props 里。

在上文的“6️⃣-④”中,我们说一共会给“ connect 方法”传递 3 个参数。前两个参数都知道是啥了,可第三个参数是什么呢?

※7️⃣在编写 TodoList.js 文件之前,我们按照实际项目的开发流程,先拆分 actionTypesactionCreators(30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

actionTypes.js 文件:

export const CHANGE_INPUT_VALUE = "change_input_value";

actionCreators.js 文件:

import {CHANGE_INPUT_VALUE} from "./actionTypes";  

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

8️⃣打开 TodoList.js 文件:

import React, {Component} from "react";
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd'; 

// ❗️从 store 中引入 actionCreators 中创建好的“方法”。
import {getInputChangeAction} from "./store/actionCreators"; 

import {connect} from "react-redux";

class TodoList extends Component {
  
  render() {
    return (
      <div style={{marginTop: "10px", marginLeft: "10px"}}>
        <div>
      
          <Input 
            value={this.props.inputValue} 
            placeholder="todo info" 
            style={{width: "300px", marginRight: "10px"}} 
            
            onChange={this.props.getInputChangeAction}
          /> {/* 8️⃣-④:给 input 绑定一个“事件 onChange”,可这个“事件”应该怎样被调用呢? */}
             {/*
              8️⃣-⑥:因此可以通过 this.props.getInputChangeAction 来“调用” store 的
              getInputChangeAction;
               */}
              
          <Button type="primary">提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.props.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />

        </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => { 
  return {  
    inputValue: state.inputValue,  
    list: state.list  
  }
}

/*
❗️❗️❗️8️⃣-②:接下来,我们定义哪些“用户的操作”
应该当作 action,并传给 store;
 */
const mapDispatchToProps = (dispatch) => { /*
																					 8️⃣-③:把 store 里的“dispatch 方法”
                                           作为“参数”传递给 mapDispatchToProps;
                                            */
  return {  
    getInputChangeAction(e) { /*
    													8️⃣-⑤:在这里定义用户的“onChange 操作”会被当作 action 
                              传给 store;
                               */
      
      const action = getInputChangeAction(e.target.value); 
      
      dispatch(action);
    }
  }
}

/*
8️⃣-①:给 connect 传递第 3 个参数——mapDispatchToProps。
❗️mapDispatchToProps 直译为:我们把 store 的 dispatch 方法“挂载”到 TodoList 的 props 上。
即,我们可以定义哪些“用户的操作”应该当作 action,并传给 store。
 */
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);  /*
																																				❗️导出一个
																																				“容器组件”!
                                                                         */

8️⃣-⑦:打开 reducer.js 文件,编写相应的代码;

// 8️⃣-⑧:引入“常量”;
import {CHANGE_INPUT_VALUE} from "./actionTypes";  

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

export default (state = defaultState, action) => { 
  
  if(action.type === CHANGE_INPUT_VALUE) {  
    
    const newState = JSON.parse(JSON.stringify(state)); 
  
    newState.inputValue = action.value;  
    
    return newState;  
  }
  
  return state;
}

返回查看页面效果: (30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

OK,以上我们算是完整地用了一遍 React-redux,它其实就是一个编程套路,不难,唯手熟尔!

在后续的实战项目中,我们还会不停地使用,所以这里一定要理解其思路,自己多写几次。

4 “提交”和“删除”功能

接下来的“提交”和“删除”功能我就不赘述了,我直接给代码,套路都是一样的,关键是理解整个思路!

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";

actionCreators.js 文件代码:

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

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

export const getAddItem = () => ({
  type: ADD_TODO_ITEM
});

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

export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index
})
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from "./actionTypes";  

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

export default (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_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;
}

TodoList.js 文件代码:

import React, {Component} from "react";
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd'; 

import {getInputChangeAction, getAddItemAction, getDeleteItemAction} from "./store/actionCreators"; 

import {connect} from "react-redux";

class TodoList extends Component {
  
  render() {
    return (
      <div style={{marginTop: "10px", marginLeft: "10px"}}>
        <div>
      
          <Input 
            value={this.props.inputValue} 
            placeholder="todo info" 
            style={{width: "300px", marginRight: "10px"}} 
            
            onChange={this.props.getInputChangeAction}
          />   
              
          <Button type="primary" onClick={this.props.handleButtonClick}>提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.props.list}
            renderItem={(item, index) => <List.Item onClick = {() => {this.props.handleItemDelete(index)}}>{item}</List.Item>}
          />

        </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => { 
  return {  
    inputValue: state.inputValue,  
    list: state.list  
  }
}


const mapDispatchToProps = (dispatch) => {  
  return {  
    getInputChangeAction(e) {  
      const action = getInputChangeAction(e.target.value); 
      dispatch(action);
    },
    
    handleButtonClick() { 
      const action = getAddItemAction();  
      dispatch(action); 
    },

    handleItemDelete(index) { 
      const action = getDeleteItemAction(index);
      dispatch(action); 
    } 

  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoList);  

看下页面效果: (30)Redux 进阶——⑥ React-redux 重写 TodoList | React 基础理论实操

当然,代码还有可以优化的地方(我们之前都实现过,不再赘述):

祝好,qdywxs ♥ you!