react语法 - 给vue开发者
Dva - react状态管理 - 给vuex开发者
React Router 5.x - 给vuex开发者
非响应式,不支持异步。
有 state、reducer(mutation)概念
创建 store
import { createStore } from "redux";
let { subscribe, dispatch, getState } = createStore(reducer, [preloadedState]);
-
reducer(state: State, action: Action): State
作用相当于 mutation,入口处只支持一个Action: { type: any, payload: any }
-
preloadedState: 初始的 state
-
getState: 获取 state,只能获取整个 state
-
dispatch(action: Action): void
: 触发 reducerAction: { type: any, payload: any }
-
subscribe(() => void)
: 监听到 dispatch 调用(reducer 执行)后回调,比如用来更新视图。
例子:
const reducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
};
let { subscribe, dispatch, getState } = createStore(reducer);
const Counter = ({ value, onIncrement, onDecrement }) => (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}>+</button>
<button onClick={onDecrement}>-</button>
</div>
);
const render = () => {
ReactDOM.render(
<Counter
value={getState()}
onIncrement={() => dispatch({ type: "INCREMENT" })}
onDecrement={() => dispatch({ type: "DECREMENT" })}
/>,
document.getElementById("root")
);
};
render();
subscribe(render);
处理复杂 reducer: combineReducers
import { combineReducers, createStore } from "redux";
const todos = (state: string[] = [], action: actionType) => {
if (action.type === "add") return [...state, action.payload];
else return state;
};
const filterKeyword = (state: string = "", action: actionType) => {
if (action.type === "filter") return action.payload || "";
else return state;
};
const reducer = combineReducers({ todos, filterKeyword });
export const store = createStore(reducer); // { todos: [], filterKeyword: '' }
npm install react-redux
import { Provider } from "react-redux";
import { createStore } from "redux";
let store = createStore(reducer, [preloadedState]); // 参数如上
ReactDOM.render(
<Provider store={store}>
{" "}
// 根部注入
<App />
</Provider>,
document.getElementById("root")
);
与 react 组件关联:使用 connect(mapStateToProps, mapDispatchToProps)(Component)
import { connect } from "react-redux";
type propsType = {
num: number,
dispatch: Dispatch,
};
const App: React.FC<propsType> = (props: propsType) => {
const { dispatch, num } = props;
return <div onClick={dispatch({ type: "add" })}>{num}</div>;
};
connect((state) => ({ num: state.a || 1 }))(comp1);
-
mapStateToProps(state, ownProps) => props
:每次存储状态更改时调用(会触发 rerender)。它接收整个存储状态,并应返回此组件需要的数据对象。 -
mapDispatchToProps :此参数可以是函数或对象。
-
如果是函数,则在创建组件时将调用一次。它将 dispatch 作为参数接收,并应返回一个对象,该对象具有 dispatch 用于分派动作的完整功能。
-
如果它是一个由动作创建者组成的对象,则每个动作创建者都将变成 prop 函数,该函数在被调用时会自动调度其动作。注意:我们建议使用此“对象简写”形式。
-
与 react 组件关联:使用 useSelector 和 useDispatch
// import { connect } from "react-redux";
import { useSelector, useDispatch } from "react-redux";
type propsType = {
num: number,
dispatch: Dispatch,
};
const App: React.FC<propsType> = (props: propsType) => {
// const { dispatch, num } = props;
const dispatch = useDispatch();
const num = useSelector((state) => state.a || 1); // 返回不是对象
return <div onClick={dispatch({ type: "add" })}>{num}</div>;
};
vuex 与 react-redux 对照表
VUEX | dva |
---|---|
根部 new Vue({store }) 注入 | <Provider store={store}><app/></Provider> |
namespace | 利用 combineReducers 分模块 |
state | reducer 上的参数 state 默认值 |
getters | 利用 connect、useSelector 整理数据 |
mutations | reducers (只有一个入口;return 完整的 state 以更新) |
actions | 需要第三方 redux-saga 支持,本质是对指定reducer劫持 |
commit | dispatch |
subscribe(监听 mutation 调用) | -- |
处理异步 redux-saga
基于 Generator 和 redux 的中间件机制
import { applyMiddleware, createStore } from 'redux';
const store = createStore(
reducer,
defualtVal,
applyMiddleware(中间件1, 中间件2 ...)
);
applyMiddleware 会把中间件们依序洋葱式包裹 dispatch。redux-thunk
、redux-sage
都是一个用来处理异步的中间件。
// 简单例子
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
// 创建sage中间件
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
// 注册异步事件们
sagaMiddleware.run(sagaFn); // * sagaFn
// sagaFn为一个方法,run时执行,一般用来(使用takeEvery)劫持dispatch
import { put, call, takeEvery, takeLatest, all } from "redux-saga/effects";
function* sagaFn() {
// 使用 takeEvery 劫持 async_add action
yield takeEvery("async_add", asyncAdd);
}
function* asyncAdd() {
const res = yield call(axios.post, "/api/test", { uid: 123 });
yield put({ type: "add", payload: res }); // 触发正常的 add dispatch
}
-
takeEvery, takeLatest
区别是后者仅执行最后一个异步方法,即不支持并发。 -
call
很像 js 原生 call(它会自己 next?) -
put
触发 dispatch -
多个 sagaFn 可以
function* allSageFn() { yield all([sagaFn1(), sagaFn2()]); } sagaMiddleware.run(allSageFn);
Redux Toolkit @reduxjs/toolkit
另一种对 Redux 的封装,此工具包中还包含一个强大的数据获取和缓存功能(RTK Query)
create-react-app 默认集成了它
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: { counter: counterReducer, …… }, // 切片们
})
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
创建 counterReducer 切片
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
add: (state) => {
// 实际上不会改变状态,因为它使用Immer库,
// 它检测对“草稿状态”的更改,并根据这些更改生成一个全新的不可变状态
state.value += 1
},
},
})
export const { add } = counterSlice.actions // 导出actions
export default counterSlice.reducer // 导出切片
调用方法:
import { useSelector, useDispatch } from 'react-redux'
import { add } from './counterSlice'
export function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<button onClick={() => dispatch(add())}>
点击+1,现在为:{count}
</button>
)
}
[TS写法在这里](https://redux-toolkit.js.org/tutorials/typescript)和这里
Redux 操作类型并不意味着对单个 slice 来说是专有的。从概念上讲,每个 slice reducer “拥有”自己的 Redux 状态,但它应该能够监听到任何动作类型。例如,“用户注销”时清除数据。???
异步操作:
-
redux-thunk:复杂同步逻辑 + 简单异步(toolkit推荐)
-
redux-saga:复杂异步逻辑,基于 ES6 generator 控制粒度更细
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: { counter: counterReducer, …… }, // 切片们
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false, // 关闭可序列化检查
}),
devTools: process.env.NODE_ENV !== "production",
})
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
// First, create the thunk
const thunkUserList = createAsyncThunk('users/query', async (pageIndex) => API.getUserList(pageIndex))
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder
.addCase(thunkUserList.pending, state => state.loading = true)
.addCase(thunkUserList.fulfilled, (state, action) => {
state.loading = false
state.entities.push(action.payload);
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(thunkUserList(123))
网友评论