转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
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 就知道这个仓库里边有 inputValue
和 list
这两个数据了;
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
文件之前,我们按照实际项目的开发流程,先拆分 actionTypes
和 actionCreators
:
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.js
和 actionCreators.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);
看下页面效果:
当然,代码还有可以优化的地方(我们之前都实现过,不再赘述):
- 将"UI 组件"和"容器组件"拆分------《Redux 进阶------① UI 组件、容器组件和无状态组件》;
- 代码优化------《React 入门------⑥ TodoList 代码优化》
祝好,qdywxs ♥ you!