React Hook 技巧

这里写一下一些 React Hook 的一些小技巧,看这个文章需要有一点的 React Hook 使用基础

React HookTypeScript

TypeScript 的好处就不说了,这里提一下怎么在 React Hook 中使用 TypeScriptReact Hook 的大部分 Hook 都使用了范型。

显式声明变量类型

// user 被识别成 any
const [user, setUser] = useState(null);
// idx 会被识别成 number,不过还是建议显式写成 number
const [idx, setIdx] = useState(0);
const [list, setList] = useState<PaperItem[]>(initList);
const [paperIdx, setPaperIdx] = useState<number>(0);
const [screenLoading, setScreenLoading] = useState<boolean>(false);
const [pageState, setPageState] = useState<PaperPageState>(
  PaperPageState.preview
);
const listRef = useRef<PaperItem[]>(list);
const paperIdRef = useRef<string>('');
const entryRef = useRef<string>('default');
// 这里如果不写,程序还真不知道你这里想写的是样式。。。
const iconStyle = useMemo<React.CSSProperties>(() => {
  if (!visible) {
    return { display: 'none' };
  }
}, [visible]);
复制代码

Hook 组件声明

类声明组件可以 React.Component<P={}, S={}, SS=any>来实现声明组件的属性和状态
例如:

import React from 'react';

interface TextComponentProps {
  initValue: string;
}

interface TextComponentState {
  length: number;
  value: string;
}

class TextComponent extends React.Component<
  TextComponentProps,
  TextComponentState
> {
  constructor(props: TextComponentProps) {
    const value = props.initValue;
    const length = value.length;
    this.state = { value, length };
  }

  render() {}
}
复制代码

React Hook 使用的是函数式组件,可以依赖 React.FunctionComponent<T> 或者采用别名 React.FC<T>T 为组件的属性范型
例如:

interface CarouselComponentProps {
  onIdxChange: (idx: number) => void;
  currPage: number;
  listLength: number;
}

const CarouselComponent: React.FC<CarouselComponentProps> = ({
  onIdxChange,
  currPage,
  listLength,
}) => {};
复制代码

如果需要 Ant Design 的那种子组件声明,可以改一下

interface CarouselComponentProps {
  onIdxChange: (idx: number) => void;
  currPage: number;
  listLength: number;
}

export interface CarouselItemProps {
  src: string;
}

interface Carousel extends React.FC<CarouselComponentProps> {
  Item: React.FC<CarouselItemProps>;
}

const CarouselComponent: Carousel = ({
  onIdxChange,
  currPage,
  listLength,
}) => {};

const CarouselItem: React.FC<CarouselItemProps> = ({ src }) => {};

CarouselComponent.Item = CarouselItem;

export default CarouselComponent;
复制代码

如果属性中需要获取 HTML 属性的话,可以继承 React.HTMLAttributes<T>
例如

// 这样组件就会获得 html div 元素的全部属性
interface CarouselComponentProps extends React.HTMLAttributes<HTMLDivElement> {
  onIdxChange: (idx: number) => void;
  currPage: number;
  listLength: number;
}
复制代码

useState 使用技巧

useState 基础用法基本都知道,这个方法返回一个元组,元组的第一项是当前的 state,第二项是返回修改 state 的方法,函数签名如下

useState<T>(initialState: T | (() => T)): [T, React.Dispatch<React.SetStateAction<T>>]
复制代码

基本使用

虽然这个 Hook 大部分时间都和类式的 this.setState 一致,但是因为函数组件闭包机制,实际上表现还是有那么的不一致
一个很简单的题目,下面两个组件,在快速点击三次后,3 秒后,控制台输出什么?

function Counter() {
  const [count, setCount] = useState(0);
  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(count);
    }, 3000);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={log}>Click me</button>
    </div>
  );
}

class Counter extends Component {
  state = { count: 0 };
  log = () => {
    this.setState({
      count: this.state.count + 1,
    });
    setTimeout(() => {
      console.log(this.state.count);
    }, 3000);
  };
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.log}>Click me</button>
      </div>
    );
  }
}
复制代码
  • 类组件输出的是 3 3 3 ,没什么问题,因为点击 3 次之后, this.state.count 变成了 3,通过 this 寻址到函数的 state{count: 3},所以输出为 3 3 3

  • 函数式组件输出为 0 1 2,函数式组件每次执行都有单独的闭包环境,保留这这次渲染的 stateprops,三次点击,渲染拆解如下:

    • 页面第一次渲染,页面看到 的 count = 0

    • 第一次点击,事件处理器获取的 count = 0count 变成 1,第二次渲染,渲染后页面的看到 count = 1

    • 第二次点击,事件处理器获取的 count = 1count 变成 2, 第三次渲染,渲染后页面看到 count = 2

    • 第三次点击,事件处理器获取的 count = 2count 变成 3, 第四次渲染,渲染后页面看到 count = 3

小技巧

如果函数执行需要最新的 state, 但是函数又是在初始闭包中定义的时候,这个时候是获取不到最新的 state 的,例如

const [count, setCount] = useState(0);
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
}, []);
复制代码

这里获取到的 count 永远都是 0,但是如果修改一下的话,就是正确的

const [count, setCount] = useState(0);
useEffect(() => {
  const id = setInterval(() => {
    setCount((c) => c + 1);
  }, 1000);
}, []);
复制代码

利用 useState 返回的回调函数的回调函数肯定返回最新的状态,可以通过这个获取最新状态,并且如果 setState 一个同样的值,不会引起子组件重新渲染

// 假设 fn 在某个闭包中运行,可以通过这样拿到最新的 count,并且不会引起子组件渲染
const fn = () => {
  let lastestCount = null;
  setCount((c) => {
    lastestCount = c;
    return c;
  });
};
复制代码

以上是关于在闭包中如何获取最新的 state,但是这个不太推荐使用,两个问题

  • 语义化太差了,读起来难受,调用 setState 不是为了改变 state
  • 虽然设置一个同样的 state 不会引起子组件重新渲染,但是组件本身还是会的

遇到这种情况,需要配合 useRef

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。`

useRef 这个说法,其实比较类似于类组件的 this,通过 useRef,可以缓存一些最新的 state 或者获取 DOM 引用
使用很简单,这样子

const Component: React.FC = () => {
  const [count, setCount] = useState<number>(0);
  const countRef = useRef<number>(count);
  // 这样
  countRef.current = count;
  // 或者这样
  useEffect(() => {
    countRef.current = count;
  }, [count]);
};
复制代码

有两种方式可以让 countRef 绑定最新的 state,看个人习惯

useEffect

这个写了太多反而不想写了,这个是 React Hook 里面最强大的一个 Hook,可以模拟 componentDidMountcomponentDidUpdatecomponentWillUnmount 三个生命周期,同样也有闭包的问题。

useCallback & useMemo & React.memo

日常中前面两个用的可能比较少,useCallback 返回一个记忆化的函数,useMemo 返回一个记忆化的值,如果 useMemo 返回的函数返回了一个函数,那和 useCallback 是一样的

useCallback(fn, []) === useMemo(() => fn, []);
复制代码

这两个可以配合 React.memo 做一些优化,比如传给子组件的回调函数

// 父组件
const Parent = () => {
  const callback = () => {
    /* 这里做一些修改父组件 state 的操作 */
  };
  return <Child calback={callback} />;
};
// 子组件
const Chlid = ({ callback }) => {
  return <div onClick={callback}>test</div>;
};
复制代码

React 的重新渲染机制可以知道,父组件重新渲染,子组件会跟着重新渲染,所以可以给子组件加上一层 React.memo,但是单单这样加还不够,React.momo 是浅比较,但是父组件这里明显重新渲染的时候,callback 也改变的,所以可以给 callback 包一层 useCallback

// 父组件
const Parent = () => {
  const callback = useCallback(() => {
    /* 这里做一些修改父组件 state 的操作 */
  }, []);
  return <Child calback={callback} />;
};
// 子组件
const Chlid = React.memo(({ callback }) => {
  return <div onClick={callback}>test</div>;
});
复制代码

注意,useCallback 必须配合 React.memo,这个例子只有其中一个都没用,不仅不能防止子组件重新渲染,还多加了一个浅比较

useContextuseReducer

这个可以参考我之前写的一个文章 useContext 和 useReducer 实现 Redux 和 React-Redux

useImperativeHandleReact.forwardRef

这两个可以实现类组件 ref 的效果,并且可以指定父组件可以引用的方法和属性,需要配合 useRef

// 父组件
const Parent = () => {
  const listRef = useRef(null);
  const fn = () => {
    if (listRef.current) {
      // 只能访问子组件实例的这四个方法
      listRef.current.push(item);
      listRef.current.pop();
      listRef.current.shift();
      listRef.current.unshift(item);
    }
  };
  return <List ref={listRef} />;
};
// 子组件
const List = React.forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    push(item) {},
    pop() {},
    shift() {},
    unshift(item) {},
  }));
});
复制代码

总结

总结的一些 React Hook 的小技巧

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: React HookReact的新增特性,它可以让你在不编写class的情况下使用state以及其他的React特性。它为已知的React概念提供了更直接的API,如props、state、context、refs以及生命周期。Hook不能在class中使用。\[1\] Hook的使用规则包括只能在函数外层调用Hook,不要在循环、条件判断或者子函数中调用,以及只能在React的函数组件和自定义Hook中调用Hook,不要在其他JavaScript函数中调用。\[3\]其中,useState是一种Hook,它允许你在React函数组件中添加state。使用useState方法时,可以通过const \[state, setState\] = useState(initialState)来声明一个state变量,并使用setState来更新该变量的值。\[3\] #### 引用[.reference_title] - *1* [【React-Hook】详解](https://blog.csdn.net/GengFuGuo/article/details/124840750)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [react 中的Hook 用法及介绍](https://blog.csdn.net/SongdanDab/article/details/128650378)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值