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

复制代码
转载请注明出处,未经同意,不可修改文章内容。

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

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 入门------② 使用 Antd 实现 TodoList 页面布局》中的版本!

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

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

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

2️⃣接着,在 src 目录下创建一个"文件夹" store ,并在 store 文件夹下创建一个"文件" index.js ------即,存放 store 代码的位置:

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

javascript 复制代码
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 文件;

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

javascript 复制代码
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()

javascript 复制代码
import { createStore } from "redux"; 

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

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

export default store; 

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

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

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

jsx 复制代码
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;

❗️❗️❗️以上的代码都和之前《Redux 入门------③ 创建 Redux 中的 store 并取得数据》一样,接下来用 React-redux "取得并显示数据"的代码就开始不一样了!

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

javascript 复制代码
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 文件:

jsx 复制代码
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 占位。
                                                          */

返回页面查看效果(页面正常运行):

3 改变数据

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

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

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

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

※7️⃣在编写 TodoList.js 文件之前,我们按照实际项目的开发流程,先拆分 actionTypesactionCreators

actionTypes.js 文件:

javascript 复制代码
export const CHANGE_INPUT_VALUE = "change_input_value";

actionCreators.js 文件:

javascript 复制代码
import {CHANGE_INPUT_VALUE} from "./actionTypes";  

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

8️⃣打开 TodoList.js 文件:

jsx 复制代码
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 文件,编写相应的代码;

javascript 复制代码
// 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;
}

返回查看页面效果:

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

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

4 "提交"和"删除"功能

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

actionTypes.jsactionCreators.js 中的代码,可以直接拷贝下面文章中实现好的成果:

🔗《Redux 入门------⑤ actionTypes 的拆分》

🔗《Redux 入门------⑥ 使用 actionCreator 统一创建 action》

actionTypes.js 文件代码:

javascript 复制代码
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 文件代码:

javascript 复制代码
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
})

reducer.js 文件代码(可直接拷贝《Redux 入门------⑤ actionTypes 的拆分》中的代码):

javascript 复制代码
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 文件代码:

javascript 复制代码
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);  

看下页面效果:

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

祝好,qdywxs ♥ you!

相关推荐
kingwebo'sZone3 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09013 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农4 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king4 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳4 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵5 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星5 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_5 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝5 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions5 小时前
2026年,微前端终于“死“了
前端·状态模式