Redux
基础
redux(Reducer+Flux)实现了大型应用组件间数据传值的问题(数据层框架)
Redux的工作流程
- React Components(借阅者)
- Action Creators(借书的这句话)
- Store(管理员)
- Reducers(记录本)
创建redux中的store
reducer中的state存放store里面的数据(笔记本)
const defaultState = { inputValue: '123', list: ['1', '2', '3'] } export default (state = defaultState, action) => { return state }
createStore函数第一个参数将reducer数据传给store
import { createStore } from 'redux' import reducer from './reducer' const store = createStore(reducer); export default store
页面引入store,通过getState方法获取state数据
constructor(props) { super(props) this.state = store.getState() }
Action和Reducer
action是一个对象,包含了type和value,store通过dispatch将action传递给store
handleInputChange(e) { const action = { type: 'change_input_value', value: e.target.value } store.dispatch(action) }
reducer接收action的type和value更新store(return newState),注意:reducer不能直接修改state(深拷贝)
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 === 'delete_todo_item') { const newState = JSON.parse(JSON.stringify(state)) newState.list.splice(action.value, 1); return newState } if (action.type === 'add_todo_item') { const newState = JSON.parse(JSON.stringify(state)) newState.list.push(newState.inputValue) newState.inputValue = '' return newState } return state }
组件需要订阅store更新,然后重新获取state
store.getState()
constructor(props) { super(props) this.handleInputChange = this.handleInputChange.bind(this) this.handleStoreChange = this.handleStoreChange.bind(this) this.state = store.getState() // 订阅store更新 store.subscribe(this.handleStoreChange) } // ...... // 重新获取state handleStoreChange() { this.setState(store.getState()) } // 点击删除item handleItemDelete(index) { const action = { type: 'delete_todo_item', value: index } store.dispatch(action) } // 点击btn添加listItem handleBtnClick() { const action = { type: 'add_todo_item' } store.dispatch(action) }
ActionTypes的拆分
- 常量或者变量的错误方便查错
- 字符串的拼写错误不好发现
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'
actionCreator
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value: value
})
export const getAddItemAction = () => ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index) => ({
type: DELETE_TODO_ITEM,
value: index
})
组件中创建action,然后派发出去
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
Redux三大原则
store是唯一的
只有store能够改变自己的内容
reducer是拿到store之前的数据,然后将更改后的数据再返回给store
store拿到数据之后自己进行更新(reducer不能更改store的内容)
reducer必需是纯函数
纯函数:给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
state和action确定,return的结果就是固定的
newState.date = new Date()
不是纯函数,异步、ajax...state.inputValue = action.value
参数修改 (副作用)也不是纯函数
进阶
UI组件和容器组件
UI组件只关注于组件的内容,容器组件负责处理组件逻辑代码
UI组件
// TodoListUI
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
class TodoListUI extends Component {
render() {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div style={{ marginBottom: '10px' }}>
<Input
value={this.props.inputValue}
placeholder="todoInfo"
style={{ width: '300px', marginRight: '20px' }}
onChange={this.props.handleInputChange}
/>
<Button type="primary" onClick={this.props.handleBtnClick}>提交</Button>
</div>
<List
style={{ width: '300px' }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item onClick={() => { this.props.handleItemDelete(index) }}>
{item}
</List.Item>
)}
/>
</div>
)
}
}
export default TodoListUI
容器组件
import React, { Component } from 'react';
import store from '../store';
import TodoListUI from './TodoListUI'
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from '../store/actionCreator'
class TodoList extends Component {
constructor(props) {
super(props)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
this.state = store.getState()
store.subscribe(this.handleStoreChange)
}
render() {
return (
< TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleBtnClick={this.handleBtnClick}
handleInputChange={this.handleInputChange}
handleItemDelete={this.handleItemDelete}
/>
)
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState())
}
handleItemDelete(index) {
const action = getDeleteItemAction(index)
store.dispatch(action)
}
handleBtnClick() {
const action = getAddItemAction()
store.dispatch(action)
}
}
export default TodoList
注意
onClick中的箭头函数参数默认是Event事件对象,index向外获取正确参数
<List
style={{ width: '300px' }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item onClick={() => { this.props.handleItemDelete(index) }}>
{item}
</List.Item>
)}
/>
无状态组件
无状态组件
- 当一个普通组件内只要一个render函数时,便可以用无状态组件替换它
- 无状态组件以函数的形式实现,性能比较(和js类)高
- 函数组件参数为props,返回JSX
import React from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
const TodoListUI = (props) => {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div style={{ marginBottom: '10px' }}>
<Input
value={props.inputValue}
placeholder="todoInfo"
style={{ width: '300px', marginRight: '20px' }}
onChange={props.handleInputChange}
/>
<Button type="primary" onClick={props.handleBtnClick}>提交</Button>
</div>
<List
style={{ width: '300px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (
<List.Item onClick={() => { props.handleItemDelete(index) }}>
{item}
</List.Item>
)}
/>
</div>
)
}
export default TodoListUI
Redux异步请求数据
actionTypes
export const INIT_LIST_ACTION = 'init_list_action'
actionCreator
import { INIT_LIST_ACTION } from './actionTypes' export const initListAction = (data) => ({ type: INIT_LIST_ACTION, data })
reducer
import { INIT_LIST_ACTION } from './actionTypes' const defaultState = { inputValue: '', list: [] } export default (state = defaultState, action) => { if (action.type === INIT_LIST_ACTION) { const newState = JSON.parse(JSON.stringify(state)) newState.list = action.data return newState } return state }
TodoList
import store from '../store'; import axios from 'axios'; import { initListAction } from '../store/actionCreator'; componentDidMount() { axios.get('/api/todolist').then((res) => { const { data } = res const action = initListAction(data) store.dispatch(action) }) }
Redux-thunk ajax
Redux-thunk
Redux-thunk实现异步请求和复杂逻辑在action的统一处理(Redux的中间件)
异步操作的代码从组件移动到action中
actionCreator原本是一个函数返回一个对象,在使用了该中间件之后:action可以是一个函数,store能够dispatch这个函数
store接收到函数会自动执行(异步数据的获取,然后改变store,又dispatch action做一次派发)
store/index.js配置
redux-devtools-extension: https://github.com/zalmoxisus/redux-devtools-extension
实现既支持redux-devtools,同时引入redux-thunk
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk';
import reducer from './reducer';
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
actionCreator
import axios from 'axios';
import { INIT_LIST_ACTION } from './actionTypes';
export const initListAction = (data) => ({
type: INIT_LIST_ACTION,
data
})
export const getTodoList = () => {
return (dispatch) => {
axios.get('/api/todolist').then((res) => {
const { data } = res
const action = initListAction(data)
dispatch(action)
})
}
}
TodoList
import { getTodoList } from '../store/actionCreator';
componentDidMount() {
// action是一个函数
const action = getTodoList()
// 调用store来dispatch action时,action函数会被自动执行
store.dispatch(action)
}
异步请求和逻辑放到actionCreator的好处
- 减少生命周期函数的代码量
- 有利于自动化测试(getTodoList函数相比生命周期函数更方便测试)
Redux中间件
redux中间件是表示在action和store中间的处理
Redux-thunk对dispatch这个方法做了升级,调用dispatch传递对象保持原样,而如果是传递函数,会先执行这个函数,在需要的时候再调用store(根据参数的不同执行不同的逻辑)
Redux-saga中间件
index.js使用配置
import { createStore, compose, applyMiddleware } from 'redux'; import reducer from './reducer'; import createSagaMiddleware from 'redux-saga'; import todoSagas from './sagas' const sagaMiddleware = createSagaMiddleware() const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware)); const store = createStore(reducer, enhancer); // sagaMiddleware运行sagas文件 sagaMiddleware.run(todoSagas) export default store
sagas.js一定要导出一个generator函数
import { takeEvery, put } from 'redux-saga/effects'; import { GET_INIT_LIST } from './actionTypes'; import axios from 'axios'; import { initListAction } from './actionCreator' function* getInitList() { // generator对异步请求要用yield等待获取res // 通过try catch语句捕获错误 try { const res = yield axios.get('/api/todolist'); const action = initListAction(res.data) // !! yield put() yield put(action) } catch (e) { console.log('todoList 网络请求失败') } } // generator 函数 function* mySaga() { // sagas文件通过takeEvery捕获action,执行对应的方法 yield takeEvery(GET_INIT_LIST, getInitList) } export default mySaga;
reducer获取action更改state
import { INIT_LIST_ACTION } from './actionTypes' const defaultState = { inputValue: '', list: [] } export default (state = defaultState, action) => { if (action.type === INIT_LIST_ACTION) { const newState = JSON.parse(JSON.stringify(state)) newState.list = action.data return newState } return state }
React-Redux
Provider:将对应store提供给其内部的所有组件
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') );
connect:将组件和store作连接(Provider内的组件)
import React, { Component } from 'react'; import { connect } from 'react-redux'; class TodoList extends Component { render() { return ( <div> <div> <input value={this.props.inputValue} onChange={this.props.changeInputValue} /> <button>提交</button> </div> <ul> <li>123</li> </ul> </div> ) } } const mapStateToProps = (state) => { return { inputValue: state.inputValue, list: state.list } } // store.dispatch, props const mapDispatchToProps = (dispatch) => { return { changeInputValue(e) { const action = { type: 'change_input_value', value: e.target.value } dispatch(action) } } } export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
mapStateToProps:定义state数据映射到props的规则
mapDispatchToProps:将store.dispatch方法映射到props上
demo
- TodoList
import React from 'react';
import { connect } from 'react-redux';
import { getAddItemAction, getInputChangeAction, getDeleteItemAction } from './store/actionCreator'
const TodoList = (props) => {
const { inputValue, list, handleClick, changeInputValue, handleDelete } = props
return (
<div>
<div>
<input value={inputValue} onChange={changeInputValue} />
<button onClick={handleClick}>提交</button>
</div>
<ul>
{
list.map((item, index) => {
// onCclick返回一个箭头函数执行,拿到正确index参数
return <li onClick={() => (handleDelete(index))} key={index}>{item}</li>
})
}
</ul>
</div>
)
}
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}
// store.dispatch, props
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = getInputChangeAction(e.target.value)
dispatch(action)
},
handleDelete(index) {
const action = getDeleteItemAction(index)
dispatch(action)
},
handleClick() {
const action = getAddItemAction()
dispatch(action)
}
}
}
// 将UI组件和数据方法结合,返回容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
- store/index.js
import { createStore } from 'redux';
import reducer from './reducer'
const store = createStore(reducer)
export default store;
- reducer
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM } from './actionTypes'
const defaultState = {
inputValue: 'hello world',
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
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_ITEM = 'add_item'
export const DELETE_ITEM = 'delete_item'
- actionCreator
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM } from './actionTypes'
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
})
export const getAddItemAction = () => ({
type: ADD_ITEM
})