- Redux引入
- Redux工作流
- 使用antd实现TodoList初试Redux
- ActionTypes的拆分
- 使用actionCreator统一创建action
- UI组件和容器组件的拆分
- 无状态组件
- Redux配合axios
- 使用Redux-thunk中间件发送ajax
- 中间件理解
- Redux-saga中间件使用
- React-Redux的使用
Redux引入
由前面的学习可以知道,React父组件给子组件传值是通过定义属性的方式传递的。大型项目中,当父子组件层级较深时,这种传值的方式显然不切实际,因为两个组件间传值可能依赖中间大量的组件。为方便数据的传递,React必须搭配其它数据层框架使用,而目前使用最多的便是Redux。
Redux的理念是,组件里的数据不再放在组件自身,而是放在一个公共区域(store),这样传递数据只需改变store中数据即可(需要数据的组件从store中取数据即为更新后的数据)。
Redux工作流
Redux工作流组成可以划分为4个部分:React Componet、Action Creators、Store、Reducers。
- React Component:即组件,使用数据的发起者;
- Action Creators:可以理解为组件如何使用数据的表达;
- Store:存储数据的地方;
- Reducers:可以理解为Store存储数据的记录,也是数据处理的逻辑中心;
当Component更新数据时,首先创建Action,然后传递给Store,Store将当前存储数据和接收的Action传递给Reducer处理。组件在订阅store后,每次store改变可以更新自己的state。
Redux设计原则:
- store唯一;
- 只有store能够改变自己的内容(并不是reducer);
- Reducer必须是纯函数(给定固定输入就固定输出);
使用antd实现TodoList初试Redux
antdesign是React的UI组件库,可以帮助我们快速实现页面布局。查看官方文档,通过npm install antd --save
进行安装。
antd使用也比较简单,首先通过import 'antd/dist/antd.css';
引入样式,然后根据官网中各组件的样式及事例代码引用即可。样式代码如下:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List, Typography } 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=>
<div>
<Input placeholder="todo info" style=/>
<Button type="primary">提交</Button>
</div>
<List
style=
bordered
dataSource={data}
renderItem={item => (
<List.Item>
{item}
</List.Item>
)}
/>
</div>
);
}
}
export default TodoList;
此时页面已经展示出比较好看的TodoList布局。下面结合Redux实现功能。
在github搜索redux可以查看redux官网。通过npm install --save redux
进行redux安装。Redux使用步骤:
- 创建Store
在src目录下创建文件夹,名为store,store下创建index.js:
import { createStore } from 'redux';
const store = createStore();
export default store;
创建store时需要reducer,因此再创建reducer.js:
const defaultState = {
inputValue: '',
list: []
}
// 接收两个参数。state表示store存储的数据。提供默认值
export default (state = defaultState, action) => {
return state;
};
修改store,创建时传reducer:
import { createStore } from 'redux';
import reducer from './reducer'
const store = createStore(reducer); // 创建store时传递reducer
export default store;
至此store已经创建完成。
- 使用store里数据填充页面:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store';
class TodoList extends Component {
constructor(props) {
super(props);
// store提供getState方法获取其中数据
this.state = store.getState();
}
render() {
return (
<div style=>
<div>
<Input value={this.state.inputValue} placeholder="todo info" style=/>
<Button type="primary">提交</Button>
</div>
<List
style=
bordered
dataSource={this.state.list} // 使用自己的state
renderItem={item => (
<List.Item>
{item}
</List.Item>
)}
/>
</div>
);
}
}
export default TodoList;
- 改变数据:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; // 引入目录下的index.js时可以省略
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
}
render() {
return (
<div style=>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style=
onChange={this.handleInputChange}
/>
<Button type="primary">提交</Button>
</div>
<List
style=
bordered
dataSource={this.state.list} // 使用自己的state
renderItem={item => (
<List.Item>
{item}
</List.Item>
)}
/>
</div>
);
}
handleInputChange(e) {
// 首先创建action
// action是一个对象。其中type描述要做的事情
const action = {
type: 'change_input_value',
value: e.target.value
}
// 然后将action传递给store
// store提供dispatch方法可以实现传递action
store.dispatch(action);
// store会自动将当前数据和action传递给reducer
}
}
export default TodoList;
const defaultState = {
inputValue: '',
list: []
}
// 此时明白,state是上次store中存储的数据,action是每次store传递过来的action
// reducer作用就是拿到当前状态数据和action后,根据action告诉store数据应该如何更新
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
// 需要注意:reducer绝不能修改原state
const newState = JSON.parse(JSON.stringify(state)); // 深拷贝
newState.inputValue = action.value;
// store会拿reducer返回的新数据替换原有数据
return newState;
}
return state;
};
此时通过redux调试窗口可以看到store中数据会随着input值变化 ,但是页面中并不更新,这是因为组件并没有更新数据。因此需要添加订阅:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
// 需要订阅store。store数据改变时,其中函数自动执行
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style=>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style=
onChange={this.handleInputChange}
/>
<Button type="primary">提交</Button>
</div>
<List
style=
bordered
dataSource={this.state.list}
renderItem={item => (
<List.Item>
{item}
</List.Item>
)}
/>
</div>
);
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleStoreChange() {
// 更新组件自己的store
this.setState(store.getState());
}
}
export default TodoList;
此时可以正常输入文本。
完成列表添加功能:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store';
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);
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style=>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style=
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style=
bordered
dataSource={this.state.list}
renderItem={item => (
<List.Item>
{item}
</List.Item>
)}
/>
</div>
);
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleBtnClick() {
const action = {
type: 'add_item',
};
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
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_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list = [...newState.list, newState.inputValue];
// newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
return state;
};
此时TodoList已经可以正常添加item。
完成列表删除功能:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store';
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);
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style=>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style=
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style=
bordered
dataSource={this.state.list}
// 渲染元素时除item外还可以传index
renderItem={(item, index) => (
<List.Item
onClick={this.handleItemDelete.bind(this, index)}
>
{item}
</List.Item>
)}
/>
</div>
);
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleBtnClick() {
const action = {
type: 'add_item',
};
store.dispatch(action);
}
handleItemDelete(index) {
const action = {
type: 'delete_item',
index
}
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
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_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;
};
ActionTypes的拆分
为防止store中和reducer中actionType因为书写不一致而造成不必要的低级错误,我们将actionTypes提出。
在store文件夹下创建actionTypes.js文件:
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';
在TodoList.js和reducers.js中引入actionTypes:
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM } from './store/actionTypes'
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM } from './actionTypes'
使用actionCreator统一创建action
在组件代码中书写action不是一种合理的方式,推荐将创建action统一书写至单独的文件中。
在store文件夹下创建actionCreators.js文件:
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM } from './actionTypes'
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
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);
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style=>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style=
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style=
bordered
dataSource={this.state.list}
// 渲染元素时除item外还可以传index
renderItem={(item, index) => (
<List.Item
onClick={this.handleItemDelete.bind(this, index)}
>
{item}
</List.Item>
)}
/>
</div>
);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
UI组件和容器组件的拆分
在之前的TodoList.js文件中,与渲染相关的元素部分与逻辑部分在同一组件,建议将比较复杂的组件拆分成负责页面渲染的UI组件和负责逻辑部分的容器组件。
新建TodoListUI.js,将原TodoList.js中render函数中内容移至该文件,进行拆分:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
import TodoListUI from './TodoListUI'
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.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
list={this.state.list}
/>
);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
import React, { Component } from 'react';
import { Input, Button, List } from 'antd';
class TodoListUI extends Component {
render() {
return (
<div style=>
<div>
<Input
value={this.props.inputValue}
placeholder="todo info"
style=
onChange={this.props.handleInputChange}
/>
<Button
type="primary"
onClick={this.props.handleBtnClick}
>
提交
</Button>
</div>
<List
style=
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item
onClick={()=>{this.props.handleItemDelete(index)}}
>
{item}
</List.Item>
)}
/>
</div>
)
}
}
export default TodoListUI;
无状态组件
TodoListUI.js目前只有一个render函数。对于这种只有render函数的组件,可以通过无状态组件的方式定义。无状态组件其实就是一个函数,接收props,返回jsx:
import React from 'react';
import { Input, Button, List } from 'antd';
const TodoListUI = (props) => {
return (
<div style=>
<div>
<Input
value={props.inputValue}
placeholder="todo info"
style=
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style=
bordered
dataSource={props.list}
renderItem={(item, index) => (
<List.Item
onClick={()=>{props.handleItemDelete(index)}}
>
{item}
</List.Item>
)}
/>
</div>
)
};
export default TodoListUI;
相比于普通组件,无状态组件性能更佳。因为类中实际上有生命周期函数等其它函数,而函数则只有render方法。
Redux配合axios
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction } from './store/actionCreators'
import TodoListUI from './TodoListUI'
import axios from 'axios';
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.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
list={this.state.list}
/>
);
}
componentDidMount() {
axios.get('/api/todolist').then(
(res)=>{
const data = res.data;
const action = initListAction(data);
store.dispatch(action);
}
);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST } from './actionTypes'
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
export const initListAction = (data) => ({
type: INIT_LIST,
data
});
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST } 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_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;
}
if (action.type === INIT_LIST) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState;
}
return state;
};
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';
export const INIT_LIST = 'init_list';
使用Redux-thunk中间件发送ajax
上面redux配合axios发送ajax时,发送ajax的逻辑也放在了组件代码中,当项目复杂时,建议将异步请求及复杂逻辑至于单独文件中,redux-thunk中间件可以使得将复杂逻辑放至action中做处理。
github搜索redux-thunk,查看官方文档,使用npm install redux-thunk --save
进行安装,同时修改创建store时代码:
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;
使用redux-thunk创建store后,创建action就不仅只能够返回对象了,还可以返回函数,在store dispatch该action时,返回的函数自动执行:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction, getTodoList } from './store/actionCreators'
import TodoListUI from './TodoListUI'
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.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
list={this.state.list}
/>
);
}
componentDidMount() {
const action = getTodoList();
// 这里调用dispatch方法时,action返回的函数会被调用
store.dispatch(action);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST } from './actionTypes'
import axios from "axios";
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
export const initListAction = (data) => ({
type: INIT_LIST,
data
});
export const getTodoList = () => {
// 返回的函数接收dispatch参数,下面直接使用dispatch方法即可
return (dispatch) => {
axios.get('/api/todolist').then(
(res)=>{
const data = res.data;
const action = initListAction(data);
dispatch(action);
}
);
};
};
实际上,拆分代码不仅为了代码整洁性和方便管理,合理的拆分代码,有助于前端完成自动化测试。
中间件理解
在未使用中间件时,action直接由store通过dispatch方法发送给reducer,而使用中间件后,如使用redux-thunk后,action不仅可以返回对象,还可以返回函数,此时在store通过dispatch将action传递给store前还可以调用函数处理额外逻辑,因此,中间件的中间实际上指的是action和store之间。
注意中间件是Redux中的概念,而不是React。
<img width=50% src=”/Users/gp/Desktop/github_projects/AdoredU.github.io/_posts/2020-01-20-React-3.assets/image-20200121171906669.png” />
Redux-saga中间件使用
同redux-thunk一样,Redux-saga也是一个用来处理异步逻辑的中间件。
在github搜索redux-saga,查看文档,使用npm install --save redux-saga
安装redux-saga。将代码恢复至使用thunk前,创建store时使用saga中间件:
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
// 使用saga将异步逻辑单独存储于一个文件
import todoSagas from './sagas';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware),
);
const store = createStore(reducer, enhancer);
// then run the saga
sagaMiddleware.run(todoSagas);
export default store;
Redux-saga将异步逻辑单独储存于一个文件,这里新建saga.js:
// saga中必须导出generator函数
function* mySaga() {
}
export default mySaga;
完成代码:
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import {getInputChangeAction, getAddItemAction, getDeleteItemAction, getInitList} from './store/actionCreators'
import TodoListUI from './TodoListUI'
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.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
list={this.state.list}
/>
);
}
componentDidMount() {
const action = getInitList();
// 使用saga时,此时除在reducer中接收action时,还可以在sagas.js中接收action
store.dispatch(action);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState());
}
}
export default TodoList;
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST, GET_INIT_LIST } from './actionTypes'
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
export const initListAction = (data) => ({
type: INIT_LIST,
data
});
// 使用saga时还是返回对象
export const getInitList = () => ({
type: GET_INIT_LIST
});
import {takeEvery, put} from 'redux-saga/effects';
import {GET_INIT_LIST} from "./actionTypes";
import axios from 'axios';
import {initListAction} from "./actionCreators";
// 这里也推荐使用generator函数
function* getInitList() {
try {
const res = yield axios.get('/api/todolist');
const action = initListAction(res.data);
yield put(action);
}catch (e) {
console.log('todolist接口请求失败!')
}
}
// saga中必须导出generator函数
function* mySaga() {
// takeEvery:捕捉每一个actionType,然后调用getInitList方法
yield takeEvery(GET_INIT_LIST, getInitList);
}
export default mySaga;
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';
export const INIT_LIST = 'init_list';
export const GET_INIT_LIST = 'get_init_list';
React-Redux的使用
React-Redux是一个第三方模块,它帮助我们在React中更加方便的使用Redux。github搜索react-redux即可查看官方文档,通过npm install --save react-redux
安装React-Redux。
把src文件夹下除index.js外文件全部删除,重新编写:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
// 引入Provider
import {Provider} from 'react-redux';
import store from './store';
const App = (
// provider将store提供给内部所有组件(使用connect API)
<Provider store={store}>
<TodoList/>
</Provider>
)
// 这里渲染App代替TodoList
ReactDOM.render(App, document.getElementById('root'));
TodoList.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TodoList extends Component {
// 使用react-redux后,无需再在构造函数里
// constructor(props) {
// super(props);
// this.state = store.getState();
// }
render() {
return (
<div>
<div>
{/*映射后这里应该为props*/}
<input
value={this.props.inputValue}
// 定义dipatch映射后无需这种方式
// onChange={this.handleInputChange.bind(this)}
onChange={this.props.changeInputValue}
/>
<button>提交</button>
</div>
<ul>
<li>hello</li>
</ul>
</div>
);
}
}
// 连接方式
// 把store数据映射到组件props。接收参数state
const mapStateToProps = (state) => {
return {
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,而是connect方法调用
// 第一个参数:参数映射
// 第二个参数:dispatch映射
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
store/index.js:
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
store/reducer.js:
const defaultState = {
inputValue: 'hello',
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;
};
完成TodoList功能:
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
import {Provider} from 'react-redux';
import store from './store';
const App = (
<Provider store={store}>
<TodoList/>
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
TodoList.js:
import React from 'react';
import {connect} from 'react-redux';
import {getInputChangeAction, getAddItemAction, getDeleteItemAction} from './store/actionCreator';
const TodoList = (props) => {
const {inputValue, list, changeInputValue, handleClick, handleDelete} = props;
return (
<div>
<div>
<input
value={inputValue}
onChange={changeInputValue}
/>
<button
onClick={handleClick}
>
提交
</button>
</div>
<ul>
{
list.map((item, index) => {
return (
<li key={index}
onClick={() => {
handleDelete(index)
}}
>
{item}
</li>
)
})
}
</ul>
</div>
);
};
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list,
}
};
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
dispatch(getInputChangeAction(e.target.value));
},
handleClick() {
dispatch(getAddItemAction());
},
handleDelete(index) {
dispatch(getDeleteItemAction())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
sotre/index.js:
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
store/actionTypes.js:
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
store/actionCreator.js:
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from "./actionTypes";
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
store/reducers.js:
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_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_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;
};