React学习记录·redux、react-redux

161 阅读6分钟

简述

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
  }
}

注意:

  1. 不要修改 state  使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。
  2. **在 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;