React实际上只是UI框架
- 通过JSX生成动态dom渲染UI
- 没有架构、没有模板、没有设计模式、没有路由、也没有数据管理
redux统一保存数据,在隔离了数据与UI的同时,负责处理数据的绑定
什么时候需要redux?
- 组件需要共享数据(或者叫做状态state)的时候
- 某个状态需要在任何地方都可以被随时访问的时候
- 某个组件需要改变另一个组件的状态时
- 语言切换、黑暗模式切换、用户登录全局数据共享等
安装 redux
yarn add redux
创建 reducer
interface LanguageState {language: 'en' | 'zh'languageList: { name: string; code: string }[]}const defaultState: LanguageState = {language: 'zh',languageList: [{ name: '中文', code: 'zh' },{ name: 'English', code: 'en' },],}const languageReducer = (state = defaultState, action) => {return state}export default languageReducer
创建 store
import { createStore } from 'redux'import languageReducer from './languageReducer'const store = createStore(languageReducer)export default store
getState
通过 store.getState 获得状态
import store from '../../redux/store'// 类组件class HeaderComponnet extends React.Component<RouteComponentProps, State> {constructor(props) {super(props);const storeState = store.getState();this.state = {language: storeState.language,languageList: storeState.languageList,};}// ...}// 函数式组件export const Header = () => {const navigate = useNavigate()const storeState = store.getState()const [language, setLanguage] = useState(storeState.language)const [languageList, setLanguageList] = useState(storeState.languageList)// ...}
dispatch
通过 store.dispatch() 更新状态
在 reducer 中添加 action 对应的处理方法
注意我们不能直接更改 state, 而是应该返回一个新的 state
export default (state = defaultState, action) => {if (action.type === 'chang_language') {const newState = { ...state, language: action.payload }return newState}return state}
使用 dispatch 更新状态
menuClickHandler = (e) => {console.log(e);const action = {type: "change_language",payload: e.key,};store.dispatch(action);};
subscribe
dispatch 之后,store 的 state 虽然变了,但是页面并不会重新渲染
需要使用subscibe订阅,当 state 变化后执行回调函数更新组件状态
// 函数式组件export const Header = () => {const navigate = useNavigate()const storeState = store.getState()const [language, setLanguage] = useState(storeState.language)const [languageList, setLanguageList] = useState(storeState.languageList)store.subscribe(() => {const newState = store.getState()setLanguage(newState.language)})// ...}// 类组件class HeaderComponnet extends React.Component<RouteComponentProps, State> {constructor(props) {super(props);const storeState = store.getState();this.state = {language: storeState.language,languageList: storeState.languageList,};store.subscribe(() => {const newState = store.getState()this.setState({language: newState.language})})}// ...}
if 改为 switch
reducer 改用 switch 逻辑更清晰
// if 写法const languageReducer = (state = defaultState, action) => {if (action.type === 'change_language') {const newState = { ...state, language: action.payload }return newState}if (action.type === 'add_language') {const newState = {...state,languageList: [...state.languageList, action.payload],}return newState}return state}
// 改成switch写法const languageReducer = (state = defaultState, action) => {switch (action.type) {case 'change_language':return { ...state, language: action.payload }case 'add_language':return {...state,languageList: [...state.languageList, action.payload],}default:return state}}
Action Creator (工厂模式)
如果任由 action 的定义分散在各组件内的事件处理函数中,将会变得非常混乱,难以维护
应将action的定义封装成函数,统一管理,这些函数称为 action creator
将负责同一功能的reducer和actionCreator都放在相同目录中
创建action creator
// action.type 都定义为常量export const CHANGE_LANGUAGE = 'change_language'export const ADD_LANGUAGE = 'add_language'export const changeLanguageActionCreator = (languageCode: 'zh' | 'en') => {return {type: CHANGE_LANGUAGE,payload: languageCode,}}export const addLanguageActionCreator = (name: string, code: string) => {return {type: ADD_LANGUAGE,payload: { name, code },}}
reducer中的action.type改为常量,防止出错
import i18n from 'i18next'import { CHANGE_LANGUAGE, ADD_LANGUAGE } from './languageActions'// ...const languageReducer = (state = defaultState, action) => {switch (action.type) {case CHANGE_LANGUAGE:i18n.changeLanguage(action.payload)return { ...state, language: action.payload }case ADD_LANGUAGE:return {...state,languageList: [...state.languageList, action.payload],}default:return state}}export default languageReducer
组件中dispatch时使用action creator
import {addLanguageActionCreator,changeLanguageActionCreator,} from '../../redux/language/languageActions'const menuClickHandler = (e) => {if (e.key === 'new') {const action = addLanguageActionCreator('新语言', 'new_lang')store.dispatch(action)} else {const action = changeLanguageActionCreator(e.key)store.dispatch(action)}}
使用 typescript 定义 action 的类型
在上面的代码中,如果在reducer中如果把 action.type 写成 action.types, action.payload 写成 action.data 也不会被发现,这时候就要使用 typescript 来定义 action 的类型,以便在代码编译前就发现错误
在action creator 中定义 action 的类型
export const CHANGE_LANGUAGE = 'change_language'export const ADD_LANGUAGE = 'add_language'interface ChangeLanguageActionCreator {type: typeof CHANGE_LANGUAGEpayload: 'zh' | 'en'}interface AddLanguageActionCreator {type: typeof ADD_LANGUAGEpayload: { name: string; code: string }}export type LanguageActionTypes = ChangeLanguageActionCreator | AddLanguageActionCreatorexport const changeLanguageActionCreator = (languageCode: 'zh' | 'en'): ChangeLanguageActionCreator => {return {type: CHANGE_LANGUAGE,payload: languageCode,}}export const addLanguageActionCreator = (name: string,code: string): AddLanguageActionCreator => {return {type: ADD_LANGUAGE,payload: { name, code },}}
在 reducer 中指定参数 action 的类型
此时 TS 根据 action.type 的值就能推断出 action.payload 的类型
