useState:
useState是一个Hook函数,让你在函数组件中拥有state变量。它接收一个初始化的state,返回是一个数组,数组里有两个元素,第一个元素是当前状态值和另一个更新该值的方法。
let _state = []
let index = 0
const myUseState = (initState) => {
const currentIndex = index
_state[currentIndex] = _state[currentIndex] ===undefined ? initState : _state[currentIndex]
const setState = (newValue =>{
_state[currentIndex] = newValue
render()
})
index += 1
return [_state[currentIndex],setState]
}
在useState hook中使用对象作为状态变量(当使用对象时,需要记住的是):
1.不可变的重要性
2.useState返回的更新方法不是像类组件中的setState合并对象
创建一个状态变量时,类组件中的this.setState 自动合并更新对象,而函数组件中useState的更新方法则是直接替换对象 。
setMessageObj((prevState) => {
return { ...prevState, message: val };
});
//使用Object.assign
setMessageObj((prevState) => {
return Object.assign({}, prevState, { message: val });
});
1.更新方法不会立即更新值
2.如果你需要用到之前的值更新状态,你必须将之前的值传递给该方法,则它会返回一个更新后的值,eg: setMessage(previousVal => previousVal + currentVal)
3.如果你使用同样的值作为当前状态,则React不会触发重新渲染。(React是用Object.is来做比较的)
4.useState不像类组件中的this.setState合并对象,它是直接替换对象。
5.useState和所有的hooks遵循相同的规则,特别是,注意这些函数的调用顺序。(可以借助 ESLint plugin,它会帮助我们强制实施这些规则)
使用useRef解决useState异步更新数据导致更新不及时问题:
import { useState, useRef, useEffect } from 'react';
export function useCallbackState(state: any) {
const cbRef = useRef();
const [data, setData] = useState(state);
useEffect(() => {
cbRef.current && cbRef.current(data);
}, [data]);
return [
data,
function (val: any, callback: any) {
cbRef.current = callback;
setData(val);
},
];
}
import { useCallbackState } from '@/utils/callbackState';
const Index: React.FC = () => {
const [searchParam, setSearchParam] = useCallbackState();
}
useMemo:
useMemo是针对一个函数,是否多次执行
useMemo主要用来解决使用React hooks产生的无用渲染的性能问题
在方法函数,由于不能使用shouldComponentUpdate处理性能问题,react hooks新增了useMemo
useMemo使用:如果useMemo(fn, arr)第二个参数匹配,并且其值发生改变,才会多次执行执行,否则只执行一次,如果为空数组[],fn只执行一次.
useCallback:
将useMemo和useCallback放在一起来说,是因为这两个Hook都具有缓存效果,它们的返回值在依赖没有变化时总是返回旧值,通常作为优化手段来使用,特别是一些高性能的计算。useMemo类似于Vue中computed的作用,useEffect类似于Vue watch的作用,useMemo和useEffect的使用场景也可以类比。
1.useMemo作为一种性能优化的手段,会在依赖更新后计算相关值,类似于Vue中computed的功能,React官网也有对其使用的建议,先编写在没有 useMemo 的情况下也可以执行的代码,之后再在你的代码中添加 useMemo,以达到优化性能的目的,即优化是有针对性的
2.useMemo会在渲染阶段执行,即函数调用时同步执行,所以要注意useMemo内部函数是不是存在构成渲染循环的逻辑
useCallback是返回memorized回调函数,有两种常见场景:
1.当把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用
2.函数组件使用React.memo,如果传递函数,那么每次都是新函数就不起作用了,所以需要useCallback.
useEffect:
useEffect通过return进行一些清除。
useEffect(()=>{
fn(),...
return ()=>{}
},arr)
传递空数组:仅在挂载和卸载的时候执行
useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。注意:有多个副效应,应该调用多个useEffect(),而不应该合并写在一起。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);
React.memo:
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。
React.memo()使用场景就是纯函数组件频繁渲染props
useReducer:
const [state, dispatch] = useReducer(reducer, initState);
useReducer接收两个参数:
第一个参数:reducer函数。第二个参数:初始化的state。返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。
使用reducer的场景:(如果你的state是一个数组或者对象; 如果你的state变化很复杂,经常一个操作需要修改很多state; 如果你希望构建自动化测试用例来保证程序的稳定性; 如果你需要在深层子组件里面去修改一些状态; 如果你用应用程序比较大,希望UI和业务能够分开维护;)
useContext:
// 定义 context函数
const LoginContext = React.createContext();
function homePage() {
const [state, dispatch] = useReducer(loginReducer, initState);
// 利用 context 共享dispatch
return ( <LoginContext.Provider value={{dispatch}}>...</LoginContext.Provider>)
}
// 子组件中直接通过context拿到dispatch,出发reducer操作state
const dispatch = useContext(LoginContext);
如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer + useContext
使用Context相比回调函数的优势:
1.对比回调函数的自定义命名,Context的Api更加明确,我们可以更清晰的知道哪些组件使用了dispatch、应用中的数据流动和变化。这也是React一直以来单向数据流的优势。
2.更好的性能:如果使用回调函数作为参数传递的话,因为每次render函数都会变化,也会导致子组件rerender。当然我们可以使用useCallback解决这个问题,但相比useCallbackReact官方更推荐使用useReducer,因为React会保证dispatch始终是不变的,不会引起consumer组件的rerender。
reducer:
语法:(state, action) => newState
1.reducer处理的state对象必须是immutable,这意味着永远不要直接修改参数中的state对象,reducer函数应该每次都返回一个新的state object.Immutable:每次都返回一个newState, 永远不要直接修改state对象
2.既然reducer要求每次都返回一个新的对象,我们可以使用ES6中的解构赋值方式去创建一个新对象,并复写我们需要改变的state属性。{ ...state, count: state.count + 1; }
3.reducer的幂等性
4.React中的state比较方案:React在比较oldState和newState的时候是使用Object.is函数,如果是同一个对象则不会出发组件的rerender。
5.action:用来表示触发的行为。
Action:一个常规的Action对象通常有type和payload(可选)组成:
type: 本次操作的类型,也是 reducer 条件判断的依据
payload: 提供操作附带的数据信息.
useSelector:
通过useSotre获取整个Store对象。
const Store = useStore()
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const counter = useSelector((state) => (state.counter),shallowEqual);
第一个参数是一个函数,返回一个对象,对象里面可以通过state获取到redux的数据。第二个值实际上是性能优化,用于决定组件是否重新渲染。
每次修改redux的数据,useSelector会默认对前后两个对象进行比较,怎么比较呢,就直接===。我们知道,两个对象即使看起来一样,但是他们的堆地址并不相同,所以全等比较的时候肯定不相等。将shallowEquall作为第二个参数传入,useSelector就会通过这个函数来进行比较,此函数进行的是浅比较。
useDispatch:
const dispatch = useDispatch();
返回dispatch方法,使用返回的dispatch方法去发送对应的action就可以了。
CSS Modules:
由于项目开发逐渐庞大过程中,对于样式有两个不得不考虑的问题(这也是CSS Modules出现的问题)
1.全局污染 —— CSS 文件中的选择器是全局生效的,不同文件中的同名选择器,根据 build 后生成文件中的先后顺序,后面的样式会将前面的覆盖;
2.选择器复杂 —— 为了避免上面的问题,我们在编写样式的时候不得不小心翼翼,类名里会带上限制范围的标识,变得越来越长,多人开发时还很容易导致命名风格混乱,一个元素上使用的选择器个数也可能越来越多。
而CSS Module就是在对className转换的时候加入一定的规则,使得样式名自动添加一个hash值,确保唯一性.
React.FC
是函数式组件,是在TypeScript使用的一个泛型