redux

2022/11/25 Redux

# Redux

# 一、redux 工作流

在这里插入图片描述

# 二、创建 redux

npm install redux

  • src/redux/languageReducer.ts 数据处理逻辑

    // 数据类型
    export interface LanguageState {
        language: "en" | "zh"
        languageList: { name: string,  code: string }[]
    }
    
    // 默认数据
    const defaultState : LanguageState = {
        language: "zh",
        languageList: [
            { name: "中文", code: "zh" },
            { name: "English", code: "en" }
        ]
    };
    
    export default = (state = defaultState, action) => {
        // console.log(state, action) 输出结果看下图
    
        switch(action.type) {
            case "change_labguage":
                return {...state, language: action.payload}
            default:
                return state
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    注:

    • 参数 state 是旧数据

    • 在 redux 中 不能直接修改参数 state , 其 state 是 immutable(不可修改的),需要将参数 state 赋值给一个新对象,在该新对象对状态进行修改。

  • src/redux/store.ts (数据仓库 store)

    import { createStore } from "redux";
    // 数据处理逻辑
    import languageReducer from "./languageReducer";
    
    const store = createStore(languageReducer);
    
    // 通过动态获取 store 数据仓库中的数据,并读取变量类型
    export type RootState = ReturnType<typeof store.getState>;
    
    export default store;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 三、在类组件中使用 redux

这里使用了高阶函数,可以使用,也可以不使用

1、自定义高阶函数 withRouter

// 路由钩子函数
import {
  useNavigate,
  NavigateFunction,
  useParams,
  ParamsFunction,
  useLocation,
  LocationFunction,
} from "react-router-dom";

// 最终通过 withRouter 函数处理后,返回的数据类型有哪些?
export interface RouteComponentProps {
  navigate: NavigateFunction;
  params: ParamsFunction;
  location: LocationFunction;
}
export const withRouter = (Component) => {
  const Wrappper = (props) => {
    const navigate = useNavigate();
    const params = useParams();
    const location = useLocation();

    return (
      <Component
        navigate={navigate}
        params={params}
        location={location}
        {...props}
      ></Component>
    );
  };
  return Wrapper;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Wrappper 是函数式组件,故可以使用到 react-router-dom@v6 中的钩子函数,并将其钩子函数通过 props 传入到 Component


2、将 header 组件封装成高阶函数,并使用 store 数据仓库

import React from 'react'

// 导入自定义好的高阶函数
import { withRoute, RouteComponentProps } from '@/helpers/withRouter'

// 导入 store 仓库
import store from "@/src/redux/store"

/*
  定义 state 的数据类型,由于在此案例中,是将 store 中的数据放入到 state.
  故 将 store 中定义的数据类型进行导出,state 继承 LanguageState
*/
import { LanguageState } from "@/src/redux/languageReducer"
interface State extends LanguageState {}

class HeaderComponent extends React.Component<RouteComponentProps, State> {
    constructor(props) {
        super(props)
        // 获取 store 数据仓库
        const storeState = store.getState()

        // 使用 store 数据(将其放入到 state)
        this.state = {
            language: storeState.language,
            languageList: storeState.languageList
        }

        // 订阅 store (当 store 数据发生改变时,会执行回调函数)
        store.subscribe(() => {
            // 重新去 store 取出新数据
            const newState = store.getState()
            this.setState({
                language: newState.language,
                languageList: newState.languageList
            })
        })
    }
    render() {
        // 使用路由信息(通过自定义高阶函数传递过来的路由信息)
        const { navigate } = this.props
        return (
            <div className="header">
                <Dropdown.Button
                    overlay={ <Menu
                                onClick={() => {this.menuClickHander}}
                                items={this.state.languageList.map(l => { key: l.code, label: l.name })} />
                            }
                > { this.state.language === "zh" ? "中文", "English" }
                </Dropdown.Button>
            </div>
        )
    }
    // 修改 state 数据参数
    menuClickHander = (e) => {
        // console.log(e)
        // this.setState({ language: e.key})
        const action = {
            type: "change_language",
            payload: e.key
        }

        // 向仓库分发修改 language 的指令
        store.dispatch(action)
    }
}

// 通过高阶函数处理后,导出
export const Header = withRoute(HeaderComponent)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
  • 事件对象 e 在这里插入图片描述

  • this.setState({ language: e.key}): 采用这种方式修改数据,也可以让数据修改,UI 也会进行同步。但数据仓库没有进行同步。

  • store.dispatch(action):在 src/redux/languageReducer.ts 中的方法输出内容为

在这里插入图片描述


# 四、在类组件中使用 react-redux

npm install redux

npm install react-redux

npm install @types/react-redux --save-dev

https://react-redux.js.org/introduction/getting-started

1、入口文件 index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
ReactDOM.render(
  <React.StrictMode store={store}>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
1
2
3
4
5
6
7
8
9
10
11

此步操作目的是在全局范围内可以操作 redux

2、header 组件

import React from 'react'

import { withRoute, RouteComponentProps } from '@/helpers/withRouter'

import { RootState } from "@/src/redux/store"

import { connect } from 'react-redux'
import { Dispatch } from 'redux'

// 将 store 中的 数据注入到 props
const mapStateToProps = (state: RootState) => {
    return {
        language: state.language,
        languageList: state.languageList
    }
}

// 将 dispatch 导入到 props
const mapDispatchToProps = (dispatch: Dispatch) => {
    return {
        changeLanguage: (code: "zh" | "en") => {
            const action = {
                  type: "change_language",
                  payload: code
            }
            dispatch(action)
        }
    }
}

type PropsType =
RouteComponentProps // react-router 路由 props 类型
&
ReturnType<typeof mapStateToProps> // redux store 类型
&
ReturnType<typeof mapDispatchToProps> // redux dispatch 类型

class HeaderComponent extends React.Component<PropsType> {
    render() {
        const { navigate } = this.props
        return (
            <div className="header">
                <Dropdown.Button
                    overlay={ <Menu
                                onClick={() => {this.menuClickHander}}
                                items={this.props.languageList.map(l => { key: l.code, label: l.name })} />
                            }
                > { this.props.language === "zh" ? "中文", "English" }
                </Dropdown.Button>
            </div>
        )
    }
    // 修改 state 数据参数
    menuClickHander = (e) => {
      this.props.changeLanguage(e.key)
    }
}

export const Header = connect(mapStateToProps, mapDispatchToProps)(withRoute(HeaderComponent))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

3、 react-reduxredux 对比

  • redux:是直接原有的 api;react-redux:是在 redux 的基础上进行封装,操作起来更方便
  • redux:在哪个组件要使用时,则导入 store;react-redux:可以实现全局导入,任何地方都可以使用
  • react-redux:更适合目前开发的流程

# 五、在函数式组件中使用 react-redux

1、入口文件 index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
ReactDOM.render(
  <React.StrictMode store={store}>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
1
2
3
4
5
6
7
8
9
10
11

2、header 组件

import React from 'react'
import { RootState } from "@/src/redux/store"

// 钩子函数
import { useSelector, useDispatch } from 'react-redux'

const Header : React.FC = () => {

    // 获取store数据
    const language = useSelector((state : RootState) => state.language)
    const languageList = useSelector((state : RootState) => state.languageList)
    const dispatch = useDispatch()

    const menuClickHander = (e) => {
        const action = {
            type: "change_language",
            payload: e.key
        }
        dispatch(action)
    }

    return (
        <div className="header">
                <Dropdown.Button
                    overlay={ <Menu
                                onClick={() => {menuClickHander}}
                                items={languageList.map(l => { key: l.code, label: l.name })} />
                            }
                > { language === "zh" ? "中文", "English" }
                </Dropdown.Button>
        </div>
    )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33