不知不觉一个星期结束了,很快就要过年了,中间休息了两天,小孩生病,我也有点感冒了,还好,我的这个 React 基础教学课程也基本结束了。大家有不明白的可以留言问我,我一定竭尽所能的帮助你。后面几节课就 React 常用的几款第三方插件进行详细的讲解。本系列课程以 React Redux
、 React Router
、Axios
、styled-components
、redux-thunk
、React Transition Group
等插件主题进行讲解。都非常重要,可以说是学习 React
绕不过去的知识内容。这节课是Redux
的内容。
官方是这样说的:React Redux
是 Redux
的官方 React UI
绑定层。它允许你的 React
组件从 Redux
存储中读取数据,并将操作调度到存储以更新状态。这话说的依然很抽象。我来说的通俗一点,就是打通组件的任通二脉,让数据在组件间无障碍共享,无需通过Props或Contex那样层层包裹就能轻松的获取到数据并且能实现更改。 这你应该能明白了吧。还是那句话,完美。
这节课的内容我会适当的引用官方的示例作讲解。官方的东西往往东西写的多,讲的让人摸不着头脑,我就在关键点上再把讲的更通俗一点就很好了。
直接在你的项目中安装,一定要在你项目的根目录中执行安装。如下所示,进入我们的项目目录:
cd my-react-app
# npm下安装方式:
npm install @reduxjs/toolkit react-redux
# Yarn安装方式:
yarn add @reduxjs/toolkit react-redux
Redux 的实现思路是这样的:把所有的组件中要使用的项目数据集中放在一个数据仓库中(store)
,甚后,用一个 Provider 组件对这个项目的根进行一次包裹, 这样,整个项目中所有的组件就都能够拿到这个store里的数据了。当这个store里数据发生改变,相当的组件也会及时的对UI进行更新。这就相当于在很多场景下把可以替换 state 和 props 的某些功能,让我们的组件结构更清晰。
Vite创建的项目中,在index.css文件中定义了暗模式,为了在本项目教学过程中更好的查看校果,我们把相关的CSS代码给屏蔽掉
/*
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
*/
首先,我们要创建这个数据仓库,所有要共享的数据都放在这个里面。如下所示
// store.jsx
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {},
})
通过configStore
函数创建了一个数据仓库并作为默认数据库导出。目前是一个空 Store
。
有了数据,当然还要提供共享数据的方法才行。React Redux
包含一个组件
,它使得组件内所有组件包括子组件都可以获取到store
中的数据:下面的App.jsx
中展示了基本的用法。
//App.jsx
import "./styles.css";
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
function App() {
return (
<Provider store={store}>
<MyApp />
</Provider>
)
}
export default App
注意,上面的store
是默认导出项,所以我们在导入时可以随意取个名。这里为store
。
什么叫数据切片呢,数据仓库就像一个大蛋糕,我们把数据根据业务需要分割成不同的类型数据块,这样,组件就可以按需取用,增强了逻辑清晰度。我们通过工具中的 createSlice
工具来创建数据切片。如下所示:
// counterSlice.jsx
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
createSlice
函数需要一个对象参数,这个对象分三部分
name:
切片在store内的数据名称。initialState:
切片数据的初始值。 本示例中相当于 store.counter.value = 0
, counter
代表了当前的切片对象的状态。reducers:
切片数据的操作方法。这些方法可以对切片数据进行更新。其语法类似于我们前几章前面讲到的 state
和setState
。其实原理是一样的。这些操作方法我们称之为动作(action)
。action的语法:
每一个action都是一个函数对象,其函数格式如下: /**
* @param state {当前切片状态的数据}
* @param action {操作类型, 应用的时候需要从外部传入到action, 后面会有示例讲解}
*/
(state, action) => {
// 状态的更新操作
}
以上面
increment
为例,(state) => { state.value += 1 }
中state
就是当前整个切片对象状态,即store.counter
;
函数体内对state
中的value
进行了更改。
综上所述, 上面的actions
中,increment
为递增value
的值,decrement
为递减value
的值, 而incrementByAmount
则是根据步幅值增加value
的值。
最后, 这个切片对象counterSlice
就有了 actions
和 reducer
两个部分,actions
代表了数据操作部分,reducer
代表了切片对象的状态。我们分别把它们导出就可以了。我们把reducer
作为默认项导出,方便后面导入。
接下来,我们把切片状态合并到store中,如下所示, 对 store.jsx
做如下更改。
//store.jsx
import { configureStore } from '@reduxjs/toolkit'
// 导入切片状态数据
import counterReducer from './counterSlice'
export default configureStore({
reducer: {
counter: counterReducer, // 合并到store中,
},
})
redux中,提供了两个非常重要的钩子(Hooks)来做为中间件实现 对store中的切片数据进行读取和更新的操作。
useDispatch:
以action
做为参数,实现对切片数据进行更新的目的。useSelectore:
实现从store中读取切片数据。示例:
//counter.jsx
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'
export function Counter() {
const count = useSelector((store) => store.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
递增
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
递减
</button>
</div>
</div>
)
}
详讲:
useSelector
已经写的很明白了,接受一个函数参数, 这个函数的返回值即是我们要从store
中读取的值, 当前为0。 参数store
即是整个数据仓库对象。递增
或 递减
按钮时,通过 diaspatch() 这个钩子,实现了 imcrement() 及 decrement() 的操作更新了切片数据。当store中的数据发生改变,也会及时的反应到相应的组件渲染中。想一想,上面的示例是不是用Redux
实现了React
中的 state
的功能? 不过,我们只是用这个示例展示了它的基本用法,当然其强大的功能肯定比state
的应用要广,要方便。 你看,只要把你想要共享的数据通过切片整合到 store,中,那么,所有函数组件就可能以过 useSelector 和 useDispatch 这两个钩子实现切片数据的读取和更新。相当的丝滑。
这里我借用官方一个 TodoList 的例子,对Redux的功能应用作一个详细的讲解,我相信,我讲的更适合我们广大初学者。上面所讲针对函数组件而言,虽然已经极大的简化的我们的应用场景,但凡事都有例外不是。接下来是针对类组件的应用做一些功能解析。这个示例相对深入一些,有不懂的给我留言。先看效果:
在src
中创建目录 Test07
, 在这个目录中再创建两个目录 components
和 redux
, components
存放组件, redux
内存放数据切片(reducers)
和 actions
等。
我们在 Test07的目录中创建一个常量定义文件,定义一些在数据过滤状态中的数据的状态:
// ./Test07/constants.jsx
export const VISIBILITY_FILTERS = {
ALL: "所有", // 表示所有数据
COMPLETED: "已完成", // 已完成的数据
INCOMPLETE: "未完成" // 未完成的数据
};
回到Redux目录, 创建以下文件,定义 reducer
的操作的类型
// ./Test07/redux/actionTypes.jsx
export const ADD_TODO = "ADD_TODO"; // 添加数据
export const TOGGLE_TODO = "TOGGLE_TODO"; // 切换数据
export const SET_FILTER = "SET_FILTER"; // 设置过滤条件
这个文件中的常量用于标识 action 在reducers中的操作类型。
下面定义action, 关于action请看参数前面讲的action语法小节的内容。这个action是一个数据对象。约定俗成的包含两个部:
// ./Test07/redux/actions.jsx
import { ADD_TODO, TOGGLE_TODO, SET_FILTER } from "./actionTypes";
let nextTodoId = 0;
// 增加事件操作,操作类型为 ADD_TODO, 指示 reducer 增加一个事项,增加的事件参数在 payload 中。
export const addTodo = lable => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
lable
}
});
// 切换事项的完成状态操作, 指示 Reducer 将 payload 中的id事项的完成状进行切换。
export const toggleTodo = id => ({
type: TOGGLE_TODO,
payload: { id }
});
// 过滤事项状态
export const setFilter = filter => ({ type: SET_FILTER, payload: { filter } });
我们定义了三个action
: addTodo
、 toggleTodo
、 seFilter
,这三个代表的 对Redux store
的三种更新操作。上面的函数定义很简单,相信大家都能看懂
接下来我们创建selector数据选择器,因为我们针对类组件的操作,所以这时不能用useSelector这个钩子。所以我们要定义这个函数文件,具体的用法后面会讲:
// ./Test07/redux/selector.js
import { VISIBILITY_FILTERS } from "../constants";
// 根据过滤条件从store中获取数据, 用于组件创建待办事件列表
export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
const { todoList } = store.todos;
const allTodos = Object.values(todoList); // 将对象转换为数组
switch (visibilityFilter) {
case VISIBILITY_FILTERS.COMPLETED: // 显示所有已完成事件
return allTodos.filter((todo) => todo.completed)
case VISIBILITY_FILTERS.INCOMPLETE: // 显示所有未办理事件
return allTodos.filter((todo) => !todo.completed)
case VISIBILITY_FILTERS.ALL: // 显示所有事件
default:
return allTodos
}
}
之前我们用数据切片(slice)方式创建的reducer, 相当于切片数据项,这里提供了另一种方式来实现。根据数据操作的类型,我们分为两个部,然后再把这两个部分集成在一个Store中。在redux目录中再创建一个目录 reducers, 在这个目录下创建文件:
// ./Test07/redux/reducers/todosReducer.jsx
import { ADD_TODO, TOGGLE_TODO } from "../actionTypes";
//数据切片的初始值。
const initialState = {
todoList: [], // 所有事项列表
byIds: {} //根据过虑条件的不同,存储相应的状态的事项列表。
};
// reducer 负责根据 Action 指定的操作类型来对数据切片做出相应的更新。
function todosReducer (state = initialState, action) {
switch (action.type) {
case ADD_TODO: {
const { id, lable } = action.payload;
return {
...state,
todoList: {
...state.todoList,
[id]: {
id,
lable,
completed: false
}
}
};
}
case TOGGLE_TODO: {
const { id } = action.payload;
return {
...state,
todoList: {
...state.todoList,
[id]: {
...state.todoList[id],
completed: !state.todoList[id].completed
}
}
};
}
default:
return state;
}
}
export default todosReducer;
你看,我们很快就用到了我们上面定义的actions
文件里面的action
了, 根据action
里的type
,执行相应的更新操作。payload
中提供了更新操作所要依赖的数据。
...state
是ES6当中的解析语法。用于对象的复制操作是相当的棒:
const nArray = [1, 2, 3];
const newArray = [...nArray]; // 复制了nArray数组
const user1 = {name: "speedx", age: 21, birthday: "2000-09-10"};
const user2 = {...user1, birthday: "2020-03-06"}
//usr2先是复制了usr1的所有属性,后面的birthday又覆盖了前面复制的birthday属性。相同的属性名后面的总会覆盖前面的值。
[id]: {...}
的用法是以变量id的值作为新属性名,比如,id
值为“usr“
, 则 [id]:{}
等同于 usr: {...}
。
这样上面的todos.jsx
中的内容就一目了然了。
相同的目录下,我们创建另一个reducer数据文件:
// ./Test07/redux/reducers/visibilityFilterReducer.jsx
import { SET_FILTER } from "../actionTypes";
import { VISIBILITY_FILTERS } from "../../constants";
const initialState = VISIBILITY_FILTERS.ALL;
const visibilityFilterReducer = (state = initialState, action) => {
switch (action.type) {
case SET_FILTER: {
return action.payload.filter;
}
default: {
return state;
}
}
};
export default visibilityFilterReducer;
现在我们在reducers
目录中创建整合上面两个切片数据的文件,合成一个store
数据仓库。创建默认文件 index.jsx
, 这样我们store里就有了两个切片数据:todos
, visibilityFilter
// ./Test07/redux/reducers/index.jsx
import { combineReducers } from "redux";
import visibilityFilter from "./reducers/visibilityFilterReducer";
import todos from "./todosReducer";
const rootReducer = combineReducers({ todos, visibilityFilter });
export default rootReducer;
为什么要用index来命名呢,index
相当于文件的默认导入文件, 当我们在导入一个导出项时,只要导向到上级目录就行了,不用明确到index
文件。如下面的store
文件, 我们导入rootReducer
只导向到目录reducers
,并没有指定 reducers/index
, 这就是默认文件的用法。
之前我们用的是 @reduxjs/toolkit
工具中的 configureStore
来整合切片数据的,这里用 combineReducers
来直接整合的。方法不同,目的相同。这很好理解。
现在切片已经整合好了,那么我们创建仓库吧。在redux
目录下创建 store.jsx
文件
// ./Test07/redux/store.jsx
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from "./reducers";
const rootStore = configureStore({
reducer: rootReducer
});
export default rootStore;
数据已经准备好了,剩下的就是数据的展示了。
我们在components目录中创建一些必要的展示组件。这些组件中使用了 MUI框架。
创建AddTodo组件
// ./Test07/components/Addtodo.jsx
import React from "react";
import Button from "@mui/material/Button";
import TextField from '@mui/material/TextField';
import { Stack } from "@mui/material";
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'
class AddTodo extends React.Component {
constructor(props) {
super(props);
this.state = { input: "" };
}
updateInput = input => {
this.setState({ input });
};
handleAddTodo = () => {
// dispatches actions to add todo
// sets state back to empty string
};
render() {
return (
<Stack direction="row" spacing={2}>
<TextField
onChange={e => this.updateInput(e.target.value)}
value={this.state.input}
/>
<Button variant="contained" onClick={this.handleAddTodo}>
添加事项
</Button>
</Stack>
);
}
}
const ConnectAddTodo = connect(null, { addTodo })(AddTodo);
export default ConnectAddTodo
Todo组件
//./Test07/components/Todo.jsx
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListItemButton from '@mui/material/ListItemButton';
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";
const Todo = ({ todo }) => (
<ListItemButton
onClick={() => { toggleTodo() } }
>
<ListItemIcon>
{todo && todo.completed ? "" : ""}{" "}
</ListItemIcon>
<ListItemText>
{ todo.content }
</ListItemText>
</ListItemButton>
);
export default Todo;
TodoList组件:
// ./Test07/components/TodoList.jsx
import Todo from "./Todo";
import List from '@mui/material/List';
const TodoList = ({ todos }) => (
<List>
{
todos && todos.length
? todos.map((todo, index) => {
return <Todo key={index} todo={todo} />;
})
: "没有待办事项! 噢耶!"
}
</List>
);
export default TodoList;
VisibilityFilters组件
// ./Test07/components/VisibilityFilters.jsx
import { Button, Stack } from "@mui/material";
import { VISIBILITY_FILTERS } from "../constants";
import Box from '@mui/material/Box';
const VisibilityFilters = ({ activeFilter }) => {
return (
<Stack spacing={2} direction="row">
{Object.keys(VISIBILITY_FILTERS).map((filterKey, index) => {
const currentFilter = VISIBILITY_FILTERS[filterKey];
return (
<Button variant="contained"
key={index}
onClick={() => { } /** waiting for setFilter handler*/}
>
{ currentFilter }
</Button>
);
})}
</Stack>
);
};
export default VisibilityFilters;
创建 TodoApp 组件,这个组件我们在Test07目录下创建
import Typography from "@mui/material/Typography";
import AddTodo from "./components/Addtodo";
import TodoList from "./components/TodoList";
import VisibilityFilters from "./components/VisibilityFilters";
import Stack from "@mui/material/Stack";
export default function TodoApp() {
return (
<Stack spacing={3} alignItems={"center"}>
<Typography variant="h1">待办事项列表</Typography>
<AddTodo />
<TodoList />
<VisibilityFilters />
</Stack>
);
}
目前,我们并没有把数据连接到组件中。只是先把 UI 框架搭建起来了。
在App.jsx中引入store, 并向我们的App中提供数据
// App.jsx
import './App.css'
import "./styles.css";
import TodoApp from './Test07/TodoApp';
import { Provider } from 'react-redux'
import store from './redux/store';
function App() {
return (
<Provider store={store}>
<TodoApp />
</Provider>
)
}
export default App
React Redux
提供了一个函数connect()
,用于从 Redux
存储中读取值(并在存储更新时重新读取值)。
该函数采用两个参数,均为可选参数:
Connect
后将状态数据整合到组件的Props中。dispatch
。dispatch
将作为参数接收。如果它是一个action
组成的对象,则每个action
都将变成一个函数,该函数在调用时自动调度其action
。const mapStateToProps = (store, ownProps) => ({
// 计算状态数据
return {
name: "data1",
todos: [],
id: "idIndex"
}
})
const mapDispatchToProps = (dispatch) => {
return {
// 其实所有的 action 都是通过 dispatch 派发下去的。
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
// 将上面生成的状态和动作进行组合生成一个新的连接函数
const connectToStore = connect(mapStateToProps, mapDispatchToProps)
//用这个连接函数与组件连接把 状态数据 和 action 数据 传递到组件的Props
const ConnectedComponent = connectToStore(Component)
connect(mapStateToProps, mapDispatchToProps)(Component)
我们首先定义mapStateToProps
函数和 mapDispatchToProps
函数, 顾名思义, mapStateToProps
的意思就是把state
数据解构到组件的 Props当中。 而 mapDispatchToProps
则是把action的动作解构到 Props中。
例如:我们在actions.jsx
中定义了多个action
. 以addTodo
为例:
// actions.jsx
...
let nextTodoId = 0;
export const addTodo = content => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
content
}
});
...
我们将它传递到Connect函数并与组件结合, 修改AddTodo
组件如下,导入 connect
和 addTodo
:
// ./Test07/components/Addtodo.jsx
import React from "react";
import Button from "@mui/material/Button";
import TextField from '@mui/material/TextField';
import { Stack } from "@mui/material";
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'
class AddTodo extends React.Component {
constructor(props) {
super(props);
this.state = { input: "" };
}
updateInput = input => {
this.setState({ input });
};
handleAddTodo = () => {
// dispatches actions to add todo
// sets state back to empty string
};
render() {
return (
<Stack direction="row" spacing={2}>
<TextField
onChange={e => this.updateInput(e.target.value)}
value={this.state.input}
/>
<Button variant="contained" onClick={this.handleAddTodo}>
添加事项
</Button>
</Stack>
);
}
}
const ConnectAddTodo = connect(null, { addTodo })(AddTodo);
export default ConnectAddTodo
这里我们并没有定义mapDispatchToProps
函数, 而是直接传递了一个 Actions
对象,有时这比定义mapDispatchToProps要简单, 省去了dispatch
的麻烦, 当直接传递Actions
对象时,就自动dispatch
。
注意最后两句,connect()函数会返回一个新的组件,我们导出的是这个连接后的新的组件。现在你应该懂了,connect的第一参数是纯数据组成的对象,而第二个参数可以是mapDispatchToProps
函数,也可以是像上面所示的 actions
对象. 想想看,这样一来,有时一个组件内的点击事件是由调用方定义的,这时我们就可以通过这个方法把定义好的事件传递进去。这样,我们连接后的组件的props中就有一个 addTodo
的属性。
我们继续修改这个组件,组件内handleAddTodo
事件目录是空的。做如下修改:
...
class AddTodo extends React.Component {
constructor(props) {
super(props);
this.state = { input: "" };
}
updateInput = input => {
this.setState({ input });
};
// 这里直接使用了props中的 addTodo 事件对象了。
handleAddTodo = () => {
this.props.addTodo(this.state.input);
this.setState({ input: "" });
};
render() {
return (
<Stack direction="row" spacing={2}>
<TextField
onChange={e => this.updateInput(e.target.value)}
value={this.state.input}
/>
<Button variant="contained" onClick={this.handleAddTodo}>
添加事项
</Button>
</Stack>
);
}
}
const ConnectAddTodo = connect(null, { addTodo })(AddTodo);
export default ConnectAddTodo;
现在我们在 input 中输入内容后点击 “添加事项" 按钮后,内容会被清空,除此以外没有什么反应。没有关系,这是因为,我们的其它组件还没有连接到 store。为了观察到store中的数据,我们对TodoList也进行连接:
import Todo from "./Todo";
import List from '@mui/material/List';
import { connect } from 'react-redux'
const TodoList = ({ todos }) => (
<List>
{
todos.length > 0
? todos.map((todo, index) => {
return <Todo key={index} todo={todo} />;
})
: "没有待办事项! 噢耶!"
}
</List>
);
const mapStateToProps = state => {
const { todoList, byIds } = state.todos || {};
console.log("todoList =>", todoList)
const todos =
byIds.length > 0
? byIds.map(todo => todoList[todo])
: Object.values(todoList);
console.log("todos =>", todos)
return { todos };
};
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
export default ConnectedTodoList;
定义 mapStateToProps ,并在 mapStateToProps 内打印state数据。mapStateToProps 返回一个todos数据,通过connect连接到组件中。这样我们就能看到数据的变化,如下所示:
我们再继续,对Todo.jsx进行修改,把数据连接进去
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import ListItemButton from '@mui/material/ListItemButton';
import { toggleTodo } from '../redux/actions';
import { connect } from 'react-redux';
const Todo = ({ todo, toggleTodo }) => {
console.log("todo =>", todo)
return (
<ListItemButton
selected={todo.completed}
onClick={() => toggleTodo(todo.id) }
>
<ListItemIcon>
{ todo.completed ? "" : ""}{" "}
</ListItemIcon>
<ListItemText>
{ todo.lable }
</ListItemText>
</ListItemButton>
)};
const ConnectedTodo = connect(null, { toggleTodo })(Todo);
export default ConnectedTodo;
让我们实现VisibilityFilters
的功能。对VisibilityFilters.jsx进行修改:
import { Button, Stack } from "@mui/material";
import { VISIBILITY_FILTERS } from "../constants";
import { connect } from 'react-redux';
import { setFilter } from '../redux/actions';
const VisibilityFilters = ({ activeFilter, setFilter }) => {
return (
<Stack spacing={2} direction="row">
{Object.keys(VISIBILITY_FILTERS).map((filterKey, index) => {
const currentFilter = VISIBILITY_FILTERS[filterKey];
return (
<Button variant={currentFilter == activeFilter ? "contained" : "outlined"}
key={index}
onClick={() => setFilter(currentFilter) }
>
{ currentFilter }
</Button>
);
})}
</Stack>
);
};
const mapStateToProps = state => {
return { activeFilter: state.visibilityFilter };
};
const ConnectedVisibilityFilter = connect(
mapStateToProps,
{ setFilter }
)(VisibilityFilters);
export default ConnectedVisibilityFilter;
这个功能应该不用过多的解释,就是对三个按钮的点击事件进行设置,通过 setFilter 这个action 更新 store.visibilityFilter状态状态值。
最后,就是根据所选的显示功能按钮,刷新事项列表。修改TodoList组件,导入selector.jsx中的功能函数,如下所示:
import Todo from "./Todo";
import List from '@mui/material/List';
import { connect } from 'react-redux';
import { getTodosByVisibilityFilter } from "../redux/selector";
const TodoList = ({ todos }) => (
<List>
{
todos.length > 0
? todos.map((todo, index) => {
return <Todo key={index} todo={todo} />;
})
: "没有待办事项! 噢耶!"
}
</List>
);
const mapStateToProps = state => {
// const { todoList, byIds } = state.todos || {};
// console.log("todoList =>", todoList)
// const todos =
// byIds.length > 0
// ? byIds.map(todo => todoList[todo])
// : Object.values(todoList);
// console.log("todos =>", todos)
const { visibilityFilter } = state;
const todos = getTodosByVisibilityFilter(state, visibilityFilter);
return { todos };
};
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
export default ConnectedTodoList;
OK, 完美收官。怎么样,Redux 是不是也没有那么难,一但掌握,就能极大的简化组件间的通信代码。 最后送上完成后的动图,是不是相当完美。