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_LANGUAGE
payload: 'zh' | 'en'
}
interface AddLanguageActionCreator {
type: typeof ADD_LANGUAGE
payload: { name: string; code: string }
}
export type LanguageActionTypes = ChangeLanguageActionCreator | AddLanguageActionCreator
export 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 的类型