react Hooks

首次渲染执行useState,获取初始值,之后重新渲染,获取的是新值,不是初始值(hooks有缓存);
这也是Hooks顺序不能变化,不能使用if语句包裹的原因;Hooks内部维护一个有单向链表,保证调用顺序,简单地移动一下指针,就可以调用下一个hook;
每个函数组件都有一个隐式的Hooks调用栈。

React 内部通过一个固定的调用顺序来管理 state 和其他副作用。如果 Hooks 可以在条件语句中使用,那么每次渲染时根据条件的不同,Hooks 的调用顺序可能会变化,这将导致 React 无法正确地追踪状态。
Hooks不能写在条件语句中的原因

为什么不维护一个键值对呢?而要维护单向链表;
遇到自定义hooks,一个组件里面多次调用自定义Hooks;此时通过 key 寻找 Hook state 的方式就会发生冲突。

1 useState

const user = { name: 'aa', age: 14 }
[user, setUser] = useState(user)

user.name = 'lisi';
setUser(user}

setUser(user} 无法更改数据,更新前后的user对象,原值和新值的引用相同,react认为值没有变化,不会重新渲染;
解决方案:setUser({...user})setUser(Object.assign({}, user))

  • 强制重新渲染:利用引用不同 console.log({} === {}); // false
const [, forceUpdate] = useState({});
const onRefresh = () => forceUpdate({}); // 放置一个新的引用

<button onClick={onRefresh}>组件强制重新渲染</button>

2 useRef

解决问题:

  1. 获取DOM元素或子组件的实例对象;
  2. 存储渲染周期之间共享的数据;(比如:上次渲染的数据和本次渲染的数据)
    注意:修改ref不会重新渲染,useEffect也不会监听到ref变化
const time = useRef(Date.now());
time.current = Date.now(); // 修改ref不会重新渲染;
console.log(time.current) // 但是打印结果会更新

useEffect(() => {
	console.log('time值发生了变化', time.current)
}, [time.current]);  // useEffect也不会监听到ref变化
const iptRef = useRef<HTMLInputElement>(null);
const getFocus = () => {
  iptRef.current?.focus();
}

<input type="text" ref={iptRef} />
<button onClick={getFocus}>点击获取焦点</button>

组件第一次渲染会调用useRef、之后渲染不会调用useRef

const { count, setCount } = useState(0);
const preCountRef = useRef();
const add = () => {
  setCount(pre => pre + 1);
  preCountRef.current = count;
}

<h1>新值是{count},旧值是{preCountRef.current}</h1>

更改ref(time.curernt = ‘’),组件不会重新渲染;
useEffect无法监听到ref的变化,所以不会重新渲染;

useEffect(() => {
  console.log('time的值发生变化');
}, [time.current]);

函数组件使用ref,没有办法获取到组件实例,可以利用 Reat.forwardRef;

3 useEffect

注意事项:

  • 不在useEffect中改变依赖项的值,会造成死循环;
  • 多个不同功能的副作用尽量分开声明,不要写到一个useEffect中;

useEffect(fn, 参数)

  • 不加参数,每次渲染都会执行;
  • 参数为空数组[],仅在首次渲染执行;(didMount)
  • 参数为[count],仅在count更改时执行;(didUpdate)
  • useEffect第一个参数fn中return的东西,是组件销毁时要执行的(一般在return中取消请求,取消定时器、延时器)
useEffect(() => {
  const controller = new AbortController();
  fetch('xxx', { signal: controller.signal })
    .then(res => res.json())
    .then(res => {
      console.log('==>res', res);
    });

  return () => controller.abort(); // 取消请求
}, []);

4 自定义Hooks(抽离状态和状态更改方法)

自定义Hooks需要以Use开头将需要的数据和方法返回,也就是暴露出来;
比如设置一个计数器;自定义Hooks写好初始计数值、计数更改方法;

  return { count, increment, decrement, reset };
 export default useCounter;

在其他组件引入

import useCounter from './useCounter';

function Counter() {
  const { count, increment, reset } = useCounter(10); // 初始值设为10
  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={reset}>重置</button>
    </div>
  );
}
export default Counter;

也可以设置一个倒计时组件;在自定义Hooks中的useEffect中进行倒计时,更改一次,调用一次setTime;这是其他组件接收到的time也会发生变化;

5 useLayoutEffect

  • useLayoutEffect在浏览器重新绘制屏幕前触发;同步执行,阻塞浏览器重新绘制;
  • useEffect在浏览器重新绘制屏幕之后触发;异步执行,不阻塞浏览器绘制;

useLayoutEffect可以解决 闪烁 问题;

useLayoutEffect(() => {
  if (num === 0) {
    setNum(Math.random() * 200);
  }
});

<h1>num:{num}</h1>
<button onClick={() => setNum(0)}>重置为0</button>

使用useLayoutEffect,页面只出现一次变化;
使用useEffect,页面出现两次变化,一次是展示0;之后展示随机数;

6 useReducer(状态管理)

const [state, dispatch] = useReducer(reducer, initState, initAction?);
  1. reducer是一个函数 (prevState, action) => newState;prevState表示旧状态,action表示行为变更;state不可直接更改不会触发重新渲染,只能通过reducer修改;刚进入组件reducer函数不会执行(因为没有dispatch)
  2. initState对象表示初始状态,也就是默认值;
  3. initAction函数是进行状态初始化时的处理,将initState传递给initAction函数进行处理;
  4. 返回值 state是状态值,dispatch是更新state的方法;

useReducer只需要调用dispatch(action),即可更新state(dispatch会触发reducer重新执行)

const initAction = (initState) => {
  return { ...initState, age: Math.round(Math.abs(initState.age)) || 18 };
}

const reducer = (prevState, action) => {
  switch (action.type) {
    case 'UPDATE_NAME':
      return { ...prevState, name: action.payload };
    default:
      return prevState;
  }
}
dispatch({ type: 'UPDATE_NAME', payload: 'xxx' });

注意:
如果子组件、孙子组件都想使用dispatch来实现状态变更;需要通过props将dispatch函数传递下去;当然也可以使用useContext来共享;

7 UseContext

const MyContext = React.createContext({});

<MyContext.Provider value={共享数据}>
  <二级组件></二级组件>
</MyContext.Provider>

二级组件及其后代组件都可以使用MyContext;传递的value数据可以是状态(count),也可以是状态变更函数(setCount),也可以是dispatch;

const ctx = useContext(MyContext);

这里可以简单做个封装

const AppContext = React.createContext({});
export const AppContextWrapper = (props) => {
  const [count, setCount] = useState(0);
  return <AppContextWrapper.Provider value={{count, setCount}}>
           {props.children}
         </AppContextWrapper.Provider>
}
<AppContextWrapper>
  <子组件></子组件>
</AppContextWrapper>

8 React.memo

React.memo包裹函数组件,被包裹的组件,只有props变化了,才会被重新渲染;

9 useMemo

某个数据只依赖某个值的变化而变化,可以使用useMemo来进行优化;避免重新计算;结果返回的是值,不是函数

const memoValue = useMemo(() => {
  return 计算得到的值;
}, [value]); 
 // 不传数组,每次渲染都重新计算;
 // 空数组,只计算一次;
 // 依赖对应的值,对应的值发生变化时会重新执行

10 useCallback

为了防止每次重新render时,反复创建相同的函数,能够节省内存开销;
如果子组件接收的props里面有函数,则不会重新渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值