在react中,有容器组件和UI组件之分,在React Hooks出现之前,UI组件我们可以使用函数,无状态组件来展示UI,而对于容器组件,函数组件就无能为力,我们依赖类组件来获取数据处理数据,并向下传递参数给UI组件进行渲染。使用React Hooks相比从前的类组件有一下几个好处:
1.代码可读性更强,原本同一块功能的代码逻辑被拆分到不同的生命周期函数中,容易使开发者不利于维护和迭代,通过React Hooks可以讲功能代码聚合,方便阅读维护。
2.组件树层级变浅(子组件即UI组件可自己编写,但父组件即容器组件必须使用connect()()创建并暴露),在原本的代码中,我们经常用HOC/render props等方式来服用组件等状态,增加功能等,无疑增加了组件树层数及渲染,而在React Hooks中,这些功能都可以通过强大的Hooks实现(不用connect()()再创建容器组件)
让我们通过下面的项目练习,了解useStore、useDispatch和useSelector的使用。最后有总结
需求:
项目结构:用hooks就不用建这个文件
count.tsx文件:
import { Select } from "antd";
import React, { useEffect } from "react";
import { useState } from "react";
import { FC } from "react";
import {
decrementAction,
incrementAction,
incrementAsyncAction,
} from "../redux/action";
import store from "../redux/store";
import { useStore, useDispatch, useSelector } from "react-redux";
import { ActionType } from "../types/actionTypes";
interface Props {
count: number;
}
// 不用useSelector和useDispatch需用Props
// const CountUI: FC<Props> = (props) => {
const CountUI: FC = () => {
const [selectNumber, setSelectNumber] = useState<number>(1);
/*一个store可以有多个countReducer。
store中的getState()方法调用可以得到store里的状态,
与useSelector不同的是需要监听状态变化手动更新视图,useSelector则是自动更新视图,
useSelector传入的函数可以加工数据,预处理数据,所有逻辑就在reducer里面处理,降低耦合度
const count = useStore<any>().getState().countReducer
*/
const count = useSelector((state: any) => state.countReducer);
const myDispatch = useDispatch<any>();
const increment = () => {
myDispatch({ type: ActionType.Increment, data: selectNumber });
};
const decrement = () => {
myDispatch({ type: ActionType.Decrement, data: selectNumber });
};
const incrementOdd = () => {
if (count % 2 !== 0) {
myDispatch(incrementAction(selectNumber));
}
};
const incrementAsync = () => {
myDispatch(incrementAsyncAction(selectNumber, 2000));
};
return (
<div>
<h1>当前求和为:{count}</h1>
<Select
defaultValue={selectNumber}
style={{ width: 120 }}
onChange={(v: number) => setSelectNumber(v * 1)}
>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
<Select.Option value="3">3</Select.Option>
</Select>
<button onClick={increment}>➕</button>
<button onClick={decrement}>➖</button>
<button onClick={incrementOdd}>当前求和为奇数再加</button>
<button onClick={incrementAsync}>异步加</button>
</div>
);
};
export default CountUI;
action.tsx文件:
import { ActionType } from "../types/actionTypes";
export const incrementAction = (v: number) => ({ type: ActionType.Increment, data: v });
export const decrementAction = (v: number) => ({ type: ActionType.Decrement, data: v });
export const incrementAsyncAction = (v: number, t: number) => {
return (dispatch:any) => {
setTimeout(() => {
dispatch(incrementAction(v));
},t);
};
};
reducer.tsx文件:
import { ActionType } from "../types/actionTypes";
function countReducer(preState: number, action: any) {
if (preState == undefined) preState = 0;
const { type, data } = action;
switch (type) {
case ActionType.Increment:
return preState + data;
case ActionType.Decrement:
return preState - data;
default:
return preState;
}
}
export default countReducer;
store.tsx文件:
/**
* 该文件专门用于暴露一个Store对象,整个应用只有一个store对象
*/
//引用createStore,专门用于创建redux中最为核心的store对象
import { legacy_createStore as createStore, combineReducers ,applyMiddleware} from "redux";
import countReducer from "./reducer";
//引用redux-thunk用于支持异步action
import thunk from 'redux-thunk'
const reducer = combineReducers({ countReducer });
export default createStore(reducer,applyMiddleware(thunk));
actionTypes.tsx文件:
export enum ActionType {
Increment,
Decrement,
}
App.tsx文件:
import React from "react";
import { FC } from "react";
// import Count from "./containers/countContainer";
import store from "./redux/store";
import { Provider } from "react-redux";
import CountUI from "./components/count";
const App: FC = () => {
return (
// 必须要用Provider包裹,并给容器组件传store
<Provider store={store}>
{/* 没有hooks的写法(类组件同样)
<Count /> */}
{/* 有hooks的写法 */}
<CountUI/>
</Provider>
);
};
export default App;
index.tsx文件:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
总结
1.useDispatch
这个hook能得到redux store 的dispatch方法引用,通常手动dispatch action
例如:
const myDispatch = useDispatch<any>();
const increment = () => {
myDispatch({ type: ActionType.Increment, data: selectNumber });
};
之前在使用 connect 的时候,我们通常使用mapDispatchToProps和actionCreator封装一下dispatch action的过程,然而使用 useDispatch()的时候却需要“手动”地调用 dispatch()方法。
然而,实际上 Redux Hooks 曾经提供一个叫 useActions()的 API 起到类似于mapDispatchToProps 和 bindActionCreators 的作用,但后来被 Dan Abramov (React和Redux的核心成员)毙掉了,主要有两个原因:
避免将 actionCreator 的 dependency 也不得不加进 useActions() 中,导致代码冗长(dependency array)
在 Hooks 的世界中,直接使用 dispatch() 和 actionCreator 让代码更直接。相反,使用类似mapDispatchToProps 和 bindActionCreators 虽然看似缩短了代码量,却让开发者一定程度上丢失了对 redux 整体数据流动的视野和理解
2.useSelector
组件可以通过uesSelector访问store中释放的state数据
const count 222= useSelector((state: any) => state.countReducer//处理数据操作);
selector的作用时根据redux的state查找、筛选、处理后获得一个或多个派生的数据。
useSelector()这个hook的参数就是selector并返回selector的计算结果。重要的是这个hook会订阅redux store(牢记),所以每次redux store有更新,useSelector()里的selector就会重新计算一次并返回新的结果,并重新渲染当前组件。
3.useStore
useStore()这个Hook可以获取到Redux store的引用,所以可以使用到更‘底层’的API,如
- getState()
- dispatch(action)
- subscribe(listener)
- replaceReducer(nextReducer)
其中useStore().dispatch等同于useDispatch()
const dispatch = useDispatch();
const store = useStore();
console.log("they are equal: " + (dispatch === store.dispatch)); // true
store中的getState()方法可以得到store中的所有redux,但它并不等于useSelector()。最大的区别在于:getState()只会获取到当前时刻的state,之后的state更新并不会导致这个歌方法被再次调用,也不会导致重新渲染。并且useSelector()传入的函数可以预处理数据。
const count111 = useStore<any>().getState().countReducer
const count 222= useSelector((state: any) => state.countReducer//处理数据操作);
因此,根据业务需求:
- 假如当前组件需要监听 redux state 的变化,并根据 redux state的更新而渲染不同的视图或者有不同行为—— 那么就应该使用 useSelector Hook
- 假如当前组件只是为了在 redux state中一次性查询某个数据/状态,并不关心(或刻意忽略)之后的更新—— 那么就应该使用 useStore().getState()
4.两者代替connect
平时我们使用redux的时候可能使用的HOC的形式,mapStateToProps和
mapDispatchToProps
加强组件,例如:
//引入connect用于链接UI组件和redux
import { connect } from "react-redux";
import { Dispatch } from "redux";
//引入countUI组件
import CountUI from "../components/count";
import { ActionType } from "../types/actionTypes";
//函数的返回值作为状态(key:value组合)返回给了UI组件----状态
function mapStateToProps(state: any) {
//这个状态是容器组件的父组件自动传过来的
return { count: state.countReducer };
}
function mapDispatchToProps(dispatch: Dispatch) {
//这个通知redux执行的方法是容器组件的父组件自动传过来的
return {
jia: (number: number) => dispatch({ type: ActionType.Increment, data: number });
jian: (number: number) => dispatch({ type: ActionType.Decrement, data: number });
};
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
我们可以用useDispatch和useSelector来实现类似的需求。如
import { Select } from "antd";
import React, { useEffect } from "react";
import { useState } from "react";
import { FC } from "react";
import {
incrementAction,
incrementAsyncAction,
} from "../redux/action";
import { useDispatch, useSelector } from "react-redux";
import { ActionType } from "../types/actionTypes";
const CountUI: FC = () => {
const [selectNumber, setSelectNumber] = useState<number>(1);
const count = useSelector((state: any) => state.countReducer);
const myDispatch = useDispatch<any>();
const increment = () => {
myDispatch({ type: ActionType.Increment, data: selectNumber });
};
const decrement = () => {
myDispatch({ type: ActionType.Decrement, data: selectNumber });
};
const incrementOdd = () => {
if (count % 2 !== 0) {
myDispatch(incrementAction(selectNumber));
}
};
const incrementAsync = () => {
myDispatch(incrementAsyncAction(selectNumber, 2000));
};
return (
<div>
<h1>当前求和为:{count}</h1>
<Select
defaultValue={selectNumber}
style={{ width: 120 }}
onChange={(v: number) => setSelectNumber(v * 1)}
>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
<Select.Option value="3">3</Select.Option>
</Select>
<button onClick={increment}>➕</button>
<button onClick={decrement}>➖</button>
<button onClick={incrementOdd}>当前求和为奇数再加</button>
<button onClick={incrementAsync}>异步加</button>
</div>
);
};
export default CountUI;
小白笔记,如有不对请指教