简介
redux
基本实现参考:redux使用和基本实现
react-redux
在react
项目中一般配合react-redux
使用
基本使用如下:
-
安装:
npm i redux react-redux --save
-
在
src/
目录下新建store/
目录 -
在
src/store/
目录下新建actionType.js
文件(action
的type
集中管理,小项目没有必要)
export const ADD_TODO = "ADD_TODO";
export const TOGGLE_TODO = "TOGGLE_TODO";
export const SET_FILTER = "SET_FILTER";
- 在
src/store/
目录下新建actions.js
文件,存放所有的action
import * as actionTypes from './actionType'
let nextId = 0
export const addTodo = content => ({
type: actionTypes.ADD_TODO,
payload: {
id: ++nextId,
content
}
})
export const toggleTodo = id => ({
type: actionTypes.TOGGLE_TODO,
payload: { id }
})
export const setFilter = filter => ({
type: actionTypes.SET_FILTER,
payload: { filter }
})
-
在
src/store/
目录下新建reducers/
目录,存放所有的reducer
-
在
src/store/reducers/
目录下新建reducer
文件,比如todos.js
import { ADD_TODO, TOGGLE_TODO } from '../actionType'
// state的初始值
const initialState = {
allIds: [],
byIds: {}
}
export default function (state = initialState, action) {
switch(action.type) {
case ADD_TODO: {
const { id, content } = action.payload
return {
// 用这种展开的语法,不去修改原始值,而是直接返回一个新值
allIds: [...state.allIds, id],
byIds: {
...state.byIds,
[id]: {
content,
completed: false
}
}
}
}
case TOGGLE_TODO: {
const { id } = action.payload
return {
...state,
byIds: {
...state.byIds,
[id]: {
...state.byIds[id],
completed: !state.byIds[id].completed
}
}
}
}
default:
return state
}
}
- 在
src/store/reducers/
目录下新建index.js
文件,合并所有的reducer
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
export default combineReducers({ todos, visibilityFilter })
- 在
src/store/
目录下新建index.js
文件,生成store
import { createStore } from 'redux'
import rootReducer from './reducers'
export default createStore(rootReducer)
- 修改
src/App.js
文件
// ...
import { Provider } from 'react-redux'
import store from './store'
// ...
function App() {
return (
<Provider store={store}>
{/* ... */}
</Provider>
)
}
export default App;
- 在组件中使用
connect
// ...
import { connect } from 'react-redux'
import { toggleTodo } from '../../store/actions'
// ...
const Todo = ({ todo, toggleTodo }) => (
{/* ... */}
)
export default connect(null, { toggleTodo })(Todo)
备注:
connect
高级组件处理之后,在props
中就有对应的state
和action
了connect
的第一个参数是将state
转为props
的方法mapStateToProps
,当然也可以直接传对象connect
的第二个参数是将dispatch
转为props
的方法mapDispatchToProps
,当然也可以直接传对象
// 只传第一个参数
const mapStateToProps = state => {
const { visibilityFilter } = state
const todos = getTodosByVisibilityFilter(state, visibilityFilter)
return { todos }
}
export default connect(mapStateToProps)(TodoList)
// 传两个参数
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList)
redux-devtools
-
redux-devtools
是谷歌浏览器的插件,方便调试 -
需要在代码中打开
-
修改
src/store/index.js
文件
// ...
export default createStore(rootReducer, window.devToolsExtension ? window.devToolsExtension() : f=>f)
使用修饰器
create-react-app
项目为例:
-
如果是
babel7
,需要下载babel-plugin-transform-decorators-legacy
:npm i babel-plugin-transform-decorators-legacy --save
-
修改
package.json
文件
{
// ...
"babel": {
// ...
"plugins": [
"transform-decorators-legacy"
// ...
]
},
// ...
}
-
如果是
babel7
,需要下载@babel/plugin-proposal-decorators
:npm i @babel/plugin-proposal-decorators --save
-
修改
package.json
文件
{
// ...
"babel": {
// ...
"plugins": [
// ...
["@babel/plugin-proposal-decorators", { "legacy": true }],
]
},
// ...
}
- 在组件中
connect
就可以使用修饰器格式了。其他的,比如withRouter
都可以用修饰器格式
// ...
@connect(
({ test }) => ({ count: test.count}),
{ testCountAdd }
)
class Demo extends React.Component {
// ...
}
export default Demo
redux-trunk
-
安装:
npm i redux-thunk --save
-
使用
applyMiddleware
添加中间件 -
使用
compose
合并中间件和redux-devtools
(只有中间件的话,就没必要用这个方法了) -
修改
src/store/index.js
文件
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
export default createStore(rootReducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f=>f
))
- 修改
src/store/action.js
,添加异步方法
// ...
export const testCountAddAsync = num => {
return dispatch => {
setTimeout(() => {
dispatch(testCountAdd(num))
}, 2000)
}
}
// ...
- 在组件中使用
@connect(
({ test }) => ({ count: test.count}),
{ testCountAddAsync }
)
class Demo extends React.Component {
// ...
}
export default Demo
redux-actions
redux-actions
简化了action
的编写
-
安装:
npm i redux-actions --save
-
修改
src/store/actions.js
文件
import { createActions } from 'redux-actions'
export default createActions({
APP: {
ADD_TODO: undefined,
TOGGLE_TODO: undefined,
SET_FILTER: undefined,
},
USER: {
LOGIN: undefined,
LOGOUT: undefined,
SET_USER_ROLE: undefined,
},
})
-
删除
src/store/actionType.js
文件 -
修改
src/store/reducers/todos.js
文件
import { handleActions } from 'redux-actions'
import Actions from '../actions'
// state的初始值
const initialState = {
allIds: [],
byIds: {}
}
export default handleActions({
[Actions.app.addTodo](state, { payload }) {
const { id, content } = payload
return {
// 用这种展开的语法,不去修改原始值,而是直接返回一个新值
allIds: [...state.allIds, id],
byIds: {
...state.byIds,
[id]: {
content,
completed: false
}
}
}
},
[Actions.app.toggleTodo](state, { payload }) {
const { id } = payload
return {
...state,
byIds: {
...state.byIds,
[id]: {
...state.byIds[id],
completed: !state.byIds[id].completed
}
}
}
},
}, initialState)
- 修改
src/store/index.js
文件,添加actions
导出
// ...
export { default as Actions } from './actions'
- 使用示例
import { useSelector, useDispatch } from 'react-redux'
import Actions from '@/store/actions'
export default function Demo(props) {
const app = useSelector(({ app }) => app)
const dispatch = useDispatch()
const onTodoClick = id => {
dispatch(Actions.app.toggleTodo({ id }))
}
// ...
}
redux-saga
一般有两个步骤:1、异步请求接口;2、将请求结果存到store中
使用的是ES6
的generator
语法
-
安装:
npm i redux-saga --save
-
修改
src/store/actions.js
文件,添加action
import { createActions } from 'redux-actions'
// 建议区分开 action 的用途,方便后期维护
// ON_XXX 开头的给 saga 用
// 其他的,比如 SET_XXX、ClEAR_XXX 等给 reducer 用
export default createActions({
// ...
DEMO: {
ON_USER_INFO: undefined,
SET_USER_INFO: undefined,
}
})
- 在
src/store/reducer/
目录下新建demo.js
文件
import { handleActions } from 'redux-actions'
import Actions from '../actions'
const INITIAL_STATE = {
userInfo: null,
}
export default handleActions({
[Actions.demo.setUserInfo](state, { payload }) {
return {
...state,
userInfo: payload
}
},
}, INITIAL_STATE)
- 修改
src/store/reducer/index.js
文件,添加demo
import { combineReducers } from 'redux'
// ...
import demo from './demo'
export default combineReducers({
// ...
demo,
})
-
在
src/store/
目录下添加saga/
目录 -
在
src/store/saga/
目录下添加demo.js
文件
import { all, takeLatest, put } from 'redux-saga/effects'
import Actions from '../actions'
// 延时,用来模拟请求
export const delay = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
};
// 模拟请求
const request = async (url, options) => {
console.log(url, options)
await delay(1000)
return {
code: 0,
data: {
name: 'wmm66',
age: 18,
}
}
}
export function* fetchUserInfo({ payload }) {
const { id } = payload || {}
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
}
}
export default all([
takeLatest(Actions.demo.onUserInfo, fetchUserInfo),
])
- 在
src/store/saga/
目录下添加index.js
文件
import { all } from 'redux-saga/effects'
import demoSaga from './demo'
export default function* rootSage() {
yield all([
demoSaga
])
}
- 修改
src/store/index.js
文件
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducer'
import rootSaga from './saga'
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const middlewares = [sagaMiddleware];
// if (process.env.NODE_ENV === 'development') {
// middlewares.push(require('redux-logger').createLogger());
// }
const enhancer = composeEnhancers(applyMiddleware(...middlewares))
const store = createStore(rootReducer, enhancer);
sagaMiddleware.run(rootSaga);
export default store
export { default as Actions } from './actions'
- 页面中使用
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Actions from '@/store/actions'
export default function Test(props) {
const { userInfo } = useSelector(({ demo }) => demo)
const dispatch = useDispatch()
const onFetchUser = () => {
dispatch(Actions.demo.onUserInfo({ id: 123 }))
}
return (
<div>
<button onClick={onFetchUser}>请求数据</button>
<div>{JSON.stringify(userInfo)}</div>
</div>
)
}
扩展
有的时候,我们需要在某些异步操作结束后(比如接口请求完成后)做某些操作
使用Promise
的时候,我们可以使用then
、await
等很方便的做到
generator
语法可以使用callback
的方式实现
基本代码如下:
// src/store/saga/demo.js文件
export function* fetchUserInfo({ payload }) {
const { id, callback } = payload || {}
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
callback && callback(res.data)
}
}
// 使用
const onFetchUser = () => {
dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => {
console.log(userInfo)
} }))
}
我们可以将其Promise
化,方便使用
const promisifySaga = () => {
return new Promise((resolve, reject) => {
dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => {
resolve(userInfo)
} }))
})
}
const onFetchUser = async () => {
const userInfo = await promisifySaga()
console.log(userInfo)
}
promisifySaga
应该是一个比较通用的方法,我们通过传参来实现
function promisifySaga({dispatch, action, params = {}}) {
return new Promise((resolve, reject) => {
if (!dispatch || !action || typeof action !== 'function') {
reject('参数错误')
return
}
dispatch(action({ ...params, callback: (res) => {
resolve(res)
} }))
})
}
const onFetchUser = async () => {
const userInfo = await promisifySaga({
dispatch,
action: Actions.demo.onUserInfo,
params: { id: 123 }
})
console.log(userInfo)
}
promisifySaga
的意思是将saga
给promise
化,我们希望这么调用promisifySaga({dispatch, action})({ id: 123 })
function promisifySaga({dispatch, action}) {
return (params = {}) => {
return new Promise((resolve, reject) => {
if (!dispatch || !action || typeof action !== 'function') {
reject('参数错误')
return
}
dispatch(action({ ...params, callback: (res) => {
resolve(res)
} }))
})
}
}
const onFetchUser = async () => {
const userInfo = await promisifySaga({
dispatch,
action: Actions.demo.onUserInfo
})({ id: 123 })
console.log(userInfo)
}
- 最终代码如下
// src/store/saga/demo.js
export function* fetchUserInfo({ payload }) {
const {
id,
callback = i => i,
} = payload || {}
// 这里推荐捕捉错误,往后抛
try {
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
callback({ status: 'success', data: res.data })
} else {
callback({ status: 'error', reason: res.msg })
}
} catch(err) {
callback({ status: 'error', reason: err })
}
}
// src/lib/utils.js
import store from '@/store'
/**
* 是通过回调函数实现的
* 如果想在异步执行后执行某些操作,对应的saga必须设置callback
* 成功:callback({ status: 'success', data: res.data })
* 失败:callback({ status: 'error', reason: err })
*/
export const promisifySaga = action => {
return (params = {}) => {
return new Promise((resolve, reject) => {
if (!action || typeof action !== 'function') {
reject('参数错误')
return
}
store.dispatch(action({
...params,
callback: ({ status, data, reason }) => {
status === 'success'
? resolve(data)
: reject(reason)
},
}))
})
}
}
// 代码中使用
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Actions from '@/store/actions'
import { promisifySaga } from '@/lib/utils'
export default function Test(props) {
const { userInfo } = useSelector(({ demo }) => demo)
const dispatch = useDispatch()
const onFetchUser = () => {
const userInfo = await promisifySaga(Actions.demo.onUserInfo)({ id: 123 })
console.log(userInfo)
}
return (
<div>
<button onClick={onFetchUser}>请求数据</button>
<div>{JSON.stringify(userInfo)}</div>
</div>
)
}
发表评论