React实际上只是UI框架

  • 通过JSX生成动态dom渲染UI
  • 没有架构、没有模板、没有设计模式、没有路由、也没有数据管理

redux统一保存数据,在隔离了数据与UI的同时,负责处理数据的绑定

什么时候需要redux?

  • 组件需要共享数据(或者叫做状态state)的时候
  • 某个状态需要在任何地方都可以被随时访问的时候
  • 某个组件需要改变另一个组件的状态时
  • 语言切换、黑暗模式切换、用户登录全局数据共享等

安装 redux

  1. yarn add redux

创建 reducer

  1. interface LanguageState {
  2. language: 'en' | 'zh'
  3. languageList: { name: string; code: string }[]
  4. }
  5. const defaultState: LanguageState = {
  6. language: 'zh',
  7. languageList: [
  8. { name: '中文', code: 'zh' },
  9. { name: 'English', code: 'en' },
  10. ],
  11. }
  12. const languageReducer = (state = defaultState, action) => {
  13. return state
  14. }
  15. export default languageReducer

创建 store

  1. import { createStore } from 'redux'
  2. import languageReducer from './languageReducer'
  3. const store = createStore(languageReducer)
  4. export default store

getState

通过 store.getState 获得状态

  1. import store from '../../redux/store'
  2. // 类组件
  3. class HeaderComponnet extends React.Component<RouteComponentProps, State> {
  4. constructor(props) {
  5. super(props);
  6. const storeState = store.getState();
  7. this.state = {
  8. language: storeState.language,
  9. languageList: storeState.languageList,
  10. };
  11. }
  12. // ...
  13. }
  14. // 函数式组件
  15. export const Header = () => {
  16. const navigate = useNavigate()
  17. const storeState = store.getState()
  18. const [language, setLanguage] = useState(storeState.language)
  19. const [languageList, setLanguageList] = useState(storeState.languageList)
  20. // ...
  21. }

dispatch

通过 store.dispatch() 更新状态
在 reducer 中添加 action 对应的处理方法
注意我们不能直接更改 state, 而是应该返回一个新的 state

  1. export default (state = defaultState, action) => {
  2. if (action.type === 'chang_language') {
  3. const newState = { ...state, language: action.payload }
  4. return newState
  5. }
  6. return state
  7. }

使用 dispatch 更新状态

  1. menuClickHandler = (e) => {
  2. console.log(e);
  3. const action = {
  4. type: "change_language",
  5. payload: e.key,
  6. };
  7. store.dispatch(action);
  8. };

subscribe

dispatch 之后,store 的 state 虽然变了,但是页面并不会重新渲染
需要使用subscibe订阅,当 state 变化后执行回调函数更新组件状态

  1. // 函数式组件
  2. export const Header = () => {
  3. const navigate = useNavigate()
  4. const storeState = store.getState()
  5. const [language, setLanguage] = useState(storeState.language)
  6. const [languageList, setLanguageList] = useState(storeState.languageList)
  7. store.subscribe(() => {
  8. const newState = store.getState()
  9. setLanguage(newState.language)
  10. })
  11. // ...
  12. }
  13. // 类组件
  14. class HeaderComponnet extends React.Component<RouteComponentProps, State> {
  15. constructor(props) {
  16. super(props);
  17. const storeState = store.getState();
  18. this.state = {
  19. language: storeState.language,
  20. languageList: storeState.languageList,
  21. };
  22. store.subscribe(() => {
  23. const newState = store.getState()
  24. this.setState({
  25. language: newState.language
  26. })
  27. })
  28. }
  29. // ...
  30. }

if 改为 switch

reducer 改用 switch 逻辑更清晰

  1. // if 写法
  2. const languageReducer = (state = defaultState, action) => {
  3. if (action.type === 'change_language') {
  4. const newState = { ...state, language: action.payload }
  5. return newState
  6. }
  7. if (action.type === 'add_language') {
  8. const newState = {
  9. ...state,
  10. languageList: [...state.languageList, action.payload],
  11. }
  12. return newState
  13. }
  14. return state
  15. }
  1. // 改成switch写法
  2. const languageReducer = (state = defaultState, action) => {
  3. switch (action.type) {
  4. case 'change_language':
  5. return { ...state, language: action.payload }
  6. case 'add_language':
  7. return {
  8. ...state,
  9. languageList: [...state.languageList, action.payload],
  10. }
  11. default:
  12. return state
  13. }
  14. }

Action Creator (工厂模式)

如果任由 action 的定义分散在各组件内的事件处理函数中,将会变得非常混乱,难以维护
应将action的定义封装成函数,统一管理,这些函数称为 action creator

将负责同一功能的reducer和actionCreator都放在相同目录中
image.png
创建action creator

  1. // action.type 都定义为常量
  2. export const CHANGE_LANGUAGE = 'change_language'
  3. export const ADD_LANGUAGE = 'add_language'
  4. export const changeLanguageActionCreator = (languageCode: 'zh' | 'en') => {
  5. return {
  6. type: CHANGE_LANGUAGE,
  7. payload: languageCode,
  8. }
  9. }
  10. export const addLanguageActionCreator = (name: string, code: string) => {
  11. return {
  12. type: ADD_LANGUAGE,
  13. payload: { name, code },
  14. }
  15. }

reducer中的action.type改为常量,防止出错

  1. import i18n from 'i18next'
  2. import { CHANGE_LANGUAGE, ADD_LANGUAGE } from './languageActions'
  3. // ...
  4. const languageReducer = (state = defaultState, action) => {
  5. switch (action.type) {
  6. case CHANGE_LANGUAGE:
  7. i18n.changeLanguage(action.payload)
  8. return { ...state, language: action.payload }
  9. case ADD_LANGUAGE:
  10. return {
  11. ...state,
  12. languageList: [...state.languageList, action.payload],
  13. }
  14. default:
  15. return state
  16. }
  17. }
  18. export default languageReducer

组件中dispatch时使用action creator

  1. import {
  2. addLanguageActionCreator,
  3. changeLanguageActionCreator,
  4. } from '../../redux/language/languageActions'
  5. const menuClickHandler = (e) => {
  6. if (e.key === 'new') {
  7. const action = addLanguageActionCreator('新语言', 'new_lang')
  8. store.dispatch(action)
  9. } else {
  10. const action = changeLanguageActionCreator(e.key)
  11. store.dispatch(action)
  12. }
  13. }

使用 typescript 定义 action 的类型

在上面的代码中,如果在reducer中如果把 action.type 写成 action.types, action.payload 写成 action.data 也不会被发现,这时候就要使用 typescript 来定义 action 的类型,以便在代码编译前就发现错误

在action creator 中定义 action 的类型

  1. export const CHANGE_LANGUAGE = 'change_language'
  2. export const ADD_LANGUAGE = 'add_language'
  3. interface ChangeLanguageActionCreator {
  4. type: typeof CHANGE_LANGUAGE
  5. payload: 'zh' | 'en'
  6. }
  7. interface AddLanguageActionCreator {
  8. type: typeof ADD_LANGUAGE
  9. payload: { name: string; code: string }
  10. }
  11. export type LanguageActionTypes = ChangeLanguageActionCreator | AddLanguageActionCreator
  12. export const changeLanguageActionCreator = (
  13. languageCode: 'zh' | 'en'
  14. ): ChangeLanguageActionCreator => {
  15. return {
  16. type: CHANGE_LANGUAGE,
  17. payload: languageCode,
  18. }
  19. }
  20. export const addLanguageActionCreator = (
  21. name: string,
  22. code: string
  23. ): AddLanguageActionCreator => {
  24. return {
  25. type: ADD_LANGUAGE,
  26. payload: { name, code },
  27. }
  28. }

在 reducer 中指定参数 action 的类型
此时 TS 根据 action.type 的值就能推断出 action.payload 的类型
image.png