Redux
是JavaScript
状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。Redux
由Flux
演变而来,避开了 Flux 的复杂性。 不管你有没有使用过它们,只需几分钟就能上手Redux
。
Redux
Redux Flow
redux 工作流注: react组件要改变store里面的数据,首先要dispatch(派发)一个action,传递给store,然后store再把之前的state数据和action转发给reducer函数,reducer接收到action和state之后,做一些处理,然后返回一个新的state(newState)给store, 之后store用新的state替换掉之前旧的state数据,store数据改变后, react组件感知到store数据改变,然后从store重新取数据,更新组件的内容,页面发生变化.
- 安装 Redux
npm i redux
- 创建 state容器
- 创建 reducer 必须一个函数
在state中引入reducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// store/index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
// store/reducer.js
const defaultState = {
inputValue: '',
list: []
}
export default (state = defaultState, action) => {
return state
}
// 创建action todolist.js
// input绑定 onChange={this.InpuChange.bind(this)}
InpuChange (e) {
const action = { // 定义action
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action) // 派发action给reducer
}
// store/reducer.js reducer接收action
export default (state = defaultState, action) => {
console.log(state, action);
if (action.type == 'change_input_value') {
// 拷贝一份state reducer可以接收state, 但是建议不要直接修改state
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState //返回给store
}
return state
}
// store进行数据替换 store中的state数据变化
// todolist.js 更新组件页面数据
store.subscribe(this.changeStore.bind(this)) // 订阅store的改变
changeStore () {
this.setState(store.getState()) // 重新获取store中state数据
}在chrome应用商店 搜索并安装 Redux DevTools Chrome 调试Redux
1
2
3
4
5// 在store.index.js 中创建store时加入这句
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)添加actionTypes.js的意义在于 用常量去表示action类型, 在dispacth和reducer中action.type不一致导致字符串不报错难以排查问题, 使用常量如果写错会报错, 且便于修改action的名称,只需要修改定时的action值即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_TODO_ITEM = 'add_todo_item'
export const DELETE_TODO_ITEM = 'delete_todo_item'
// reducer.js
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
export default (state = defaultState, action) => {
// store 和 input的value实现响应 reducer可以接收state,
if (action.type === CHANGE_INPUT_VALUE) {
// 拷贝一份state 一定不能直接修改state 需要返回一个新的state数据
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
}
// todoList.js
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
InpuChange (e) {
const action = {
type: CHANGE_INPUT_VALUE,
value: e.target.value
}
store.dispatch(action)
}使用actionCreator统一创建action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// actionCreators.js
import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'
export const getChangeInputValue = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddTodoItem = () => ({
type: ADD_TODO_ITEM
})
export const getDeleteItem = (index) => ({
type: DELETE_TODO_ITEM,
index
})
// TodoList.js
import { getChangeInputValue, getAddTodoItem, getDeleteItem } from './store/actionCreators'
InpuChange(e) {
store.dispatch(getChangeInputValue(e.target.value))
}
buttonClick() {
store.dispatch(getAddTodoItem())
}
itemDeleteHandle(index) {
store.dispatch(getDeleteItem(index))
}combineReducers对reducer进行拆分管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// /common/header/store/reducer.js
const defaultState = {
focused: false
}
export default (state = defaultState, action ) => {
let newState = {...state} // 拷贝新对象
if (action.type === 'focus_input') {
newState.focused = true
}
if (action.type === 'blur_input') {
newState.focused = false
}
return newState
}
// /store/reducer.js
import { combineReducers } from 'redux'
import headerReducer from '../common/header/store/reducer'
const reducer = combineReducers({
header: headerReducer
})
export default reducer
// header/index.js
const mapStateToProps = (state) => {
return {
focused: state.header.focused
}
}使用Immutable.js来管理store中的state数据使其不可更改
npm i immutable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// header/reducer.js
import * as actionCreators from './actionTypes'
import { fromJS } from 'immutable'
const defaultState = fromJS({
focused: false
})
export default (state = defaultState, action ) => {
if (action.type === actionCreators.SEARCH_FOCUS) {
// immutable对象色set方法,返回一个全新的对象
return state.set('focused',true)
}
if (action.type === actionCreators.SEARCH_BLUR) {
return state.set('focused', false)
}
return state
}
// heade/index.js
const mapStateToProps = (state) => {
return {
// 使用get()获取immutable对象下的state属性
focused: state.header.get('focused')
}
}使用redux-immutable统一数据格式
npm i redux-immutable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// /store/reducer.js
// import { combineReducers } from 'redux'
import { combineReducers } from 'redux-immutable'
import headerReducer from '../common/header/store/reducer'
const reducer = combineReducers({
header: headerReducer
})
export default reducer
// heade/index.js
const mapStateToProps = (state) => {
return {
// 使用get()获取immutable对象下的state属性
// focused: state.get('header').get('focused')
focused: state.getIn(['header', 'focused']
}
}redux的三要素 store是唯一的 只有store能够改变自己的内容 Reducer必须是纯函数
- 纯函数指的是给固定的输入,就一定会有固定的输出,不管调用多少次,而且不会有任何的副作用
使用Redux-thunk中间件实现ajax请求(实际上是对dispatch的升级)
- npm i redux-thunk -S
在创建redux时,配置redux-thunk,redux-devtools
1
2
3
4
5
6
7
8
9
10import { 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
创建action,就可以使用函数作为返回值了1
2
3
4
5
6
7
8
9
10
11
12
13
14// actionCreator.js
export const initTodoList = (data) => ({
type: INIT_TODOLIST,
data
})
export const getTodoList = () => {
// 接收 dispatch 参数,直接使用,相当于调用store.dispatch方法
return (dispatch) => {
axios.get('/list.json').then( res => {
console.log(res.data)
dispatch(initTodoList(res.data))
})
}
}在组件
componentDidMount
钩子函数中调用getTodoList1
2
3componentDidMount () {
store.dispatch(getTodoList())
}reducer接收store转发的state和action,处理改变state
1
2
3
4
5
6
7
8// reducer.js
export default (state = defaultState, action) => {
if (action.type === INIT_TODOLIST) {
const newState = JSON.parse(JSON.stringify(state))
newState.list = action.data
return newState
}
}
使用Redux-saga中间件处理异步请求
- npm i redux-saga -S
在创建redux时,配置redux-saga,redux-devtools
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// index.js redux
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga'
import todoSaga from './todoSaga' // saga文件处理异步请求 必须是一个generator函数
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.run(todoSaga) // 执行todosaga
export default store;在
actionCreator
创建action 在actionTypes
创建常量GET_INIT_LIST
1
2
3
4
5
6// actionTypes.js
export const GET_INIT_LIST = 'get_init_list'
// actionCreator.js
export const getInitList = () => ({
type: GET_INIT_LIST
})创建todoSaga.js文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { takeEvery, put } from 'redux-saga/effects'
import { GET_INIT_LIST } from './actionTypes'
import axios from 'axios'
import { initTodoList } from './actionCreators'
function* fetchList () {
try {
const res = yield axios.get('/list.json')
yield put(initTodoList(res.data))
} catch (err) {
consoloe.log(err)
}
}
function* todoSaga () {
yield takeEvery(GET_INIT_LIST, fetchList)
}
export default todoSaga在组件
componentDidMount
钩子函数中调用getInitList1
2
3
4import { getInitList } from './store/actionCreators'
componentDidMount () {
store.dispatch(getInitList())
}reducer接收store转发的state和action,处理改变state
1
2
3
4
5
6
7
8// reducer.js
export default (state = defaultState, action) => {
if (action.type === INIT_TODOLIST) {
const newState = JSON.parse(JSON.stringify(state))
newState.list = action.data
return newState
}
}
React-redux的作用是为了在项目中使用Redux更加方便。
npm install --save react-redux
使用
Provider
提供器包裹组件1
2
3
4
5
6
7
8
9
10
11
12// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import store from './store'
import { Provider } from 'react-redux'
import Todo from './Todo'
const App = (
<Provider store={store}>
<Todo />
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));使用
connect
将组件和store
连接起来1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// Todo.js
import React from 'react'
import { connect } from 'react-redux'
class Todo extends React.Component {
render () {
return (
<div>
<div>
<input value={this.props.inputValue} onChange={this.props.changeInputValue}/>
<button>提交</button>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return { inputValue: state.inputValue }
}
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue (e) {
dispatch({
type: 'change_input_value',
value: e.target.value
})
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todo)reducer接收store转发的state和action,处理改变state
1
2
3
4
5
6
7
8
9
10
11// reducer.js
const defaultState = {
inputValue: ''
}
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
}