Redux并不是只能在React应用中使用,而是可以在一般的应用中使用。
一、首先来看一下redux的工作流:
ReactComponents
可以看做'借书人',ActionCreators
可以看做‘我要借本书’,Store
可以看做图书管理员,Reducers
可以看做'电脑/手册'
- store是唯一的,只有store能够改变自己的内容。唯一改变state的方法是触发
action
- 借书人(ReactComponents)通过触发action告诉图书管理员(Store)我要借本书(ActionCreators)。图书管理员(Store)收到消息后通过查阅电脑/手册(Reducers),将书(state)借给借书人(ReactComponents)
- action实质上是一个对象。我们通过ActionCreators统一创建action(定义很多个方法,每个方法都导出这个action对象)
const action = {
type: INPUT_CHANGE,
value: e.target.value
}
// store.dispatch(action)
// actionCreators.js
export const onInputChange = (value) => ({
type: INPUT_CHANGE,
value
})
// store.dispatch(onInputChange(e.target.value))
- store.dispatch(action) 当store接收到action后,会将previousState和action自动转发给reducer, reducer处理完成相应的逻辑后将新修改的newState返回给store。store接收到reducer传来的新的state后会替换掉原来旧的state,从而实现store里的state的更新
- reducer可以接收state,但绝不能修改state。所以要将从store接收到的state深拷贝一份,返回新更改的state
- reducer必须是纯函数。纯函数指的是,给定固定的输入,就一定会有固定的输出。同时不会有任何副作用。reducer里不能有new Date()、setTimeOut、 ajax请求等
const defaultStore = {
inputValue: ''
}
export default (state = defaultStore, action) => {
if (action.type === INPUT_CHANGE) {
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
return state
}
- store.subscribe()这个方法是在组件里订阅store.只要store一改变,subscribe里的方法就会自动执行
constructor (props) {
super(props)
this.state = store.getState()
store.subscribe(this.handelStoreChange)
}
handelStoreChange () {
this.setState(store.getState())
}
二、store中的方法
Redux中的store是一个保存整个应用state对象树的对象,其中包含了几个方法,它的原型如下:
type Store = {
dispatch: Dispatch
getState: () => State
subscribe: (listener: () => void) => () => void
replaceReducer: (reducer: Reducer) => void
}
- dispatch 用于发送action(动作)使用的的方法
- getState 取得目前state的方法
- subscribe 注册一个回调函数,当state有更动时会调用它
- replaceReducer 高级API,用于动态加载其它的reducer,一般情况不会用到
三、使用redux-thunk中间件实现ajax请求
- redux-thunks是redux的中间件。我们在创建store的时候,就使用中间件。
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const Combine = compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, Combine)
export default store
- 使用了redux-thunk之后,actionCreater可以return出去一个函数(正常情况下return一个对象)
export const initListData = (list) => ({
type: INIT_LIST,
list
})
export const getListData = () => {
return dispatch => {
axios.get('/api/todolist')
.then((res) => {
const data = res.data
dispatch(initListData(data))
})
}
}
- 只有使用了thunk这个中间件,action才能是个函数,当store发现action是个函数时,会自动执行这个函数
componentDidMount () {
// axios.get('/api/todolist')
// .then((res) => {
// console.log(res.data);
// store.dispatch(initListData(res.data))
// })
store.dispatch(getListData())
}
当我们把异步请求放在组件里的生命周期里时,随着代码量的增加,这个生命周期函数可能会变得越来越复杂,组件变得越来越大。所以建议还是将这种复杂的业务逻辑异步代码拆分到一个地方去管理。
现在借助redux-thunk 将异步请求放在actionCreator中去管理。
放在这里又带来一个好处,就是自动化测试变得很简单,比测试组件的生命周期函数简单的多。
- redux中间件指的是action和store之间。action通过dispatch方法被传递给store。实际上中间件就是对dispatch方法的一个封装。
最原始的dispatch方法,接收到一个对象之后会把这个对象传递给store.当有了中间件(对dispatch方法进行升级之后),当传递一个函数时,dispatch不会把这个函数直接传给store,而是先执行这个函数.所以dispatch会根据参数的不同做不同的事情。
四、使用redux-saga中间件实现ajax请求
- redux-thunk中间件是把异步操作放在action里,redux-suga是单独把异步逻辑拆分出来放到一个文件里去管理.
- saga里面都是generator函数
- saga.js中会接收到每一次派发出的action
- takeEvery函数接收到相应的action类型,会执行第二个参数的方法,将相应的异步逻辑写在第二个参数的方法中。建议写成generator函数。
import {createStore, applyMiddleware, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer'
import todoSaga from './saga'
const sagaMiddleware = createSagaMiddleware()
const enhancers = compose(applyMiddleware(sagaMiddleware),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, enhancers)
sagaMiddleware.run(todoSaga)
export default store
componentDidMount () {
store.dispatch(getListData())
}
export const initListData = (list) => ({
type: INIT_LIST,
list
})
export const getListData = () => ({
type: GRT_INIT
})
import {put, takeEvery} from 'redux-saga/effects'
import {GRT_INIT} from './actiontypes'
import {initListData} from './actionCreators'
import axios from 'axios'
function* asyncGetList () {
try {
const res = yield axios.get('/api/todolist')
const action = initListData(res.data)
yield put(action)
} catch (err) {
console.error(err)
}
}
function* todoSaga () {
yield takeEvery(GRT_INIT, asyncGetList)
}
export default todoSaga
- takeEvery和takeLatest的区别:
takeEvery每次触发都会执行,不会中断;
takeLatest连续触发,只执行最后一次触发的结果(比如点击提交按钮,避免提交多次) - 调用promise的地方用call语句包裹起来
function* fetchUser() {
const user = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users');
console.log(user)
}
-
并发请求可以使用
yeild all([ ])
把多个请求包起来
-
当saga函数特别多时如何组织文件?
方法一:
使用fork关键字运行函数,上面例子相当于:
因为userSagas打印出来是个对象,值是watchFetchUser函数
方法二:在各自文件入口维护好sage并导出来
然后在根saga文件中使用
yeild all [ ]
五、react-redux的使用
- provider将store提供给内部所有的组件。渲染根组件时使用即可。
<Provider />
是由 React Redux 提供的高阶组件,用来让你将 Redux 绑定到 React
import React from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './App';
let store = createStore(todoApp)
ReactDOM .render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
- 在组件里,使用connect,让组件和store做连接。
- connect接收3个参数,第三个参数是要和store连接的组件。mapStateToProps这个映射规则,是将store里的state映射为组件里的props。mapDispatchToProps将store.dispatch方法挂载到props上。