简述
react的状态管理工具,作用等于vuex。但不同于vuex被挂在在vue根节点上,实现数据变动,组件自动刷新渲染。react-redux要实现这种效果,还需其它辅助手段Provider、connect。
三大原则
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
})
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
//每一个 case 都可提取为一个 reducer
function rootReducer(state = 1, action) {
switch (action.type) {
case 'ADD_TODO':
return state + action.data;
case 'RESET':
return action.data
default:
return state
}
}
使用
import { createStore } from "redux"
// 根reducer
// defaultState 默认的state值
// enhancer 扩展(自定义store值、dispatch等),非必传
const defaultState = 1;
const store = createStore(rootReducer, defaultState, enhancer);
// action 最终传入rootReducer,使用action.type判断执行什么操作
const action={
type:"ADD_TODO",
data:100
}
store.dispatch(action);
// 结果
// state=101
Action
首先,让我们来给 action 下个定义。
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch()将 action 传到 store(通过reducer进行数据修改)。
const ADD_TODO = 'ADD_TODO'
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
除了 type 字段外,action 对象的结构完全由你自己决定。
Action 创建函数
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
在 Redux 中的 action 创建函数只是简单的返回一个 action:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
store.dispatch(addTodo("传入数据至action创建函数,返回action,然后传入dispatch"));
store 里能直接通过store.dispatch()调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用,后文有示例。
异步 Action
applyMiddleware配合Redux Thunk。 也可以自定义 enhancer
Reducer
Reducer 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
注意:
- 不要修改
state。 使用Object.assign()新建了一个副本。不能这样使用Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用{ ...state, ...newState }达到相同的目的。 - **在
default情况下返回旧的state。**遇到未知的 action 时,一定要返回旧的state。
enhancer
createStore的第三个可选参数
const store = createStore(rootReducer, defaultState, enhancer);
简单示例
const enhancer = function (createStore: any) {
// preloadedState 为 defaultState
return function (reducer: any, preloadedState: any) {
const store = createStore(reducer, preloadedState);
const _dispatch = store.dispatch;
// 自定义dispatch
store.dispatch = function <T extends action.All>(action: T) {
_dispatch(action);
console.log(action.type, action.data);
return action;
}
return store;
}
}
applyMiddleware就是一种enhancer,Redux Thunk等中间件以此发挥作用,Middleware 的详解查看
React-redux
react-redux的实现原理: Redux作为一个通用的模块,主要还是用来应用项目中state的变更,通过react-redux做连接,可以在React+Redux的项目中将两者结合的更好。
React-redux是一个轻量级的封装库,主要有两个核心方法实现:
Provider
Provider是react-redux给react提供的一个组件,从外部封装了整个应用,并向connect模块传递store
import { Provider } from 'react-redux';
class APP extents React.component {
render (
return (
<div className='APP'>
<Provider store={ store }>
<Header/>
</Provider>
</div>
)
);
}
export default APP;
CONNECT
connect是react-redux提供的第二个核心API,即让本组件与store做连接,映射到props当中;
1、包装原组件,将state和action通过props的方式传入到原组件内部
2、监听store变化,使其包装的原组件可相应state变化。
import { connect } from 'react-redux';
class Detail extends React.Component {
render(){
// this.props.xxx
// this.props.SET_X("");
}
}
const mapStateToProps = (state) => ({
// state 为 store传的getState()执行值
return {
// 此处的返回值会传递给props
xxx:state.xxx
}
});
const mapDispatchToProps= (dispatch) => ({
// SET_X 会传递给props
return {
SET_X: (value: string) => {
dispatch(setX(value))
}
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Detail);
用生命周期 getDerivedStateFromProps 更新 props的变化,从而更新UI
/**
* 生命周期,父级传入的props变化后触发
* @param nextProps
* @param preState
* @returns
*/
static getDerivedStateFromProps(nextProps: Props, preState: State) {
if (nextProps.syncX !== preState.syncX) {
return { syncX: nextProps.syncX };
}
return null;
}
示例
action --> reducer --> createStore --> connect --> Provider
actionType.ts
// const 断言 SET_USERINFO值为setUserInfo,且类型为setUserInfo TypeScript 3.4
// const x = 'x'; // has the type 'x'
// let y = 'x'; // has the type string
export const SET_USERINFO = "setUserInfo";
// export type SET_USERINFO = typeof SET_USERINFO;
export const SET_X = "setX";
action.ts
import * as actionType from "./actionType"
export const setUserInfo = (userInfno: any) => {
return <const>{
type: actionType.SET_USERINFO,
data: userInfno
}
}
export const setX = (x: string) => {
return <const>{
type: actionType.SET_X,
data: x
}
}
export type All = ReturnType<typeof setUserInfo> | ReturnType<typeof setX>
reducers.ts
import * as actionType from "./actionType"
import * as action from "./actions"
function setUserInfo(state: any, action: action.All) {
state.userinfo = action.data;
return state;
}
function setX(state: any, action: action.All) {
state.syncX = action.data;
return { ...state };
}
const rootReducer = (state: any, action: action.All) => {
switch (action.type) {
case actionType.SET_USERINFO:
return setUserInfo(state, action);
case actionType.SET_X:
return setX(state, action);
default:
return state
}
}
export default rootReducer;
store.ts
import { createStore } from "redux"
import * as action from "./actions"
import rootReducer from "./reducers"
// interface StateField {
// userInfo?: Object
// syncX?: string
// isLogin(): boolean
// }
const defaultState = {
userInfo: undefined,
syncX: "X",
isLogin() {
return !!this.userInfo
}
};
const enhancer = function (createStore: any) {
return function (reducer: any, preloadedState: any) {
const store = createStore(reducer, preloadedState);
const _dispatch = store.dispatch;
// 自定义dispatch
store.dispatch = function <T extends action.All>(action: T) {
_dispatch(action);
console.log(action.type, action.data);
return action;
}
return store;
}
}
export default createStore(rootReducer, defaultState, enhancer);
login.tsx
import { Component, ReactNode } from "react"
import { connect } from "react-redux";
import { Input, Button } from "antd";
import { setX } from "@/redux/actions"
interface Props {
syncX: string,
SET_X: (s: string) => void
}
interface State {
x: string,
syncX: string,
}
class Login extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.onChange = this.onChange.bind(this);
this.state = {
x: "",
syncX: this.props.syncX
}
}
/**
* 生命周期,父级传入的props变化后触发
* @param nextProps
* @param preState
* @returns
*/
static getDerivedStateFromProps(nextProps: Props, preState: State) {
if (nextProps.syncX !== preState.syncX) {
return { syncX: nextProps.syncX };
}
return null;
}
onChange(e: any) {
this.setState({
x: e.target.value
});
this.props.SET_X(e.target.value);
}
render(): ReactNode {
return (
<div>
<Input value={this.state.x} onChange={this.onChange}></Input>
<div>X: {this.state.x}</div>
<div>syncX: {this.state.syncX}</div>
</div>
);
}
}
const mapStateToProps = (state: any) => {
return {
syncX: state.syncX
}
}
const mapDispatchToProps = (dispatch: any) => {
return {
SET_X: (value: string) => {
dispatch(setX(value))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
App.tsx
import './App.less';
import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
import { Provider } from 'react-redux';
import Login from '@pages/login/login';
import store from "./redux"
function ToLogin() {
const navigate = useNavigate();
return (
<Button onClick={() => { navigate("/login") }}>跳转登录页</Button>
);
}
function App() {
return (
<div className="App">
<Provider store={store}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login></Login>}></Route>
</Routes>
</BrowserRouter>
</Provider>
</div>
);
}
export default App;