分析的源码来自https://github.com/alibaba/ho...

useUrlState

简介

useUrlState是一个通过url query来管理state的Hook。url query,即网页url的问号后面跟着的一串属性值。该hook应用主要在以下两个方面:

  • 将组建的属性值提升到url进行管理。
  • 利用该hook来管理url的query。

useUrlState可以做到不刷新页面即可改变query参数,使用起来比较方便。

useUrlState有个比较特殊的地方在于:使用它是需要独立安装@ahooksjs/use-url-state库的,源码中它也与其他hook存放在了不同的位置。可能是因为useUrlState需要使用react-router和query-string,因此按需下载,避免无意义的多下载几个库。

基本用法

它的用法比较简单,同useSetState类似,初始值都为对象中的键值对;使用如setState(即result的第二个参数)传入需要改变的键值对即可,也可传入函数;不同之处在于无法增加属性值。

简单用例

该用例来自官方文档,实现了其基本用法:将状态同步到 url query 中。然后通过设置值为 undefined,可以将属性恢复为一开始传入的默认值。

import React from 'react';
import useUrlState from '@ahooksjs/use-url-state';

export default () => {
  const [state, setState] = useUrlState({ count: '1' });

  return (
    <>
      <button
        style={{ marginRight: 8 }}
        type="button"
        onClick={() => setState({ count: Number(state.count || 0) + 1 })}
      >
        add
      </button>
      <button type="button" onClick={() => setState({ count: undefined })}>
        clear
      </button>
      <div>state: {state?.count}</div>
    </>
  );
};

源码分析

首先是一些接口和类型的定义。

//...省略部分

const rc = tmp as any;

//options参数接口:
export interface Options {
  navigateMode?: 'push' | 'replace';//状态变更时切换 history 的方式
  parseOptions?: ParseOptions;//query-string parse 的配置
  stringifyOptions?: StringifyOptions;//query-string stringify 的配置
}

//parseOptions的默认定义
const baseParseConfig: ParseOptions = {
  parseNumbers: false,
  parseBooleans: false,
};

//stringifyOptions的默认定义
const baseStringifyConfig: StringifyOptions = {
  skipNull: false,
  skipEmptyString: false,
};

//定义类型UrlState为<string, any>键值对
type UrlState = Record<string, any>;

对传入的参数进行了一定的默认值合并处理,接着获取当前url的location对象,获取传入的心state值,区分react-router的5或6版本来对新旧state值进行合并。

对步骤的详细解析写于代码中的注释部分。

const useUrlState = <S extends UrlState = UrlState>(
  initialState?: S | (() => S),
  options?: Options,
) => {

  type State = Partial<{ [key in keyof S]: any }>;
  const { navigateMode = 'push', parseOptions, stringifyOptions } = options || {};

  const mergedParseOptions = { ...baseParseConfig, ...parseOptions };
  const mergedStringifyOptions = { ...baseStringifyConfig, ...stringifyOptions };

//useLocation返回表示当前页面的location对象。location对象包含有关当前 URL 的信息。
//下面用到的有:search、hash

  const location = rc.useLocation();

  // react-router v5
  const history = rc.useHistory?.();
  // react-router v6
  const navigate = rc.useNavigate?.();

//起到强制组件重新渲染的作用
  const update = useUpdate();

  const initialStateRef = useRef(
    typeof initialState === 'function' ? (initialState as () => S)() : initialState || {},
  );

//当location.search改变的时候,将url的query转换成对象
//在本系列上一篇文章中已经解释过useMemo的作用,有选择性地进行重新执行,减少消耗。
  const queryFromUrl = useMemo(
    () => {
    return parse(location.search, mergedParseOptions);
  }, [location.search]);

//当queryFromUrl改变的时候,返回目前的query值
  const targetQuery: State = useMemo(
    () => ({
      ...initialStateRef.current,
      ...queryFromUrl,
    }),
    [queryFromUrl],
  );

//获取传入的newstate值,并且支持传入函数
  const setState = (s: React.SetStateAction<State>) => {
    const newQuery = typeof s === 'function' ? s(targetQuery) : s;

    // 1. 如果 setState 后,search 没变化,就需要 update 来触发一次更新。
    // 2. update 和 history 的更新会合并,不会造成多次更新。
    update();
    if (history) {
      history[navigateMode]({
        hash: location.hash,
        //合并新旧state,并利用stringify进行转换
        search: stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
      });
    }
    if (navigate) {
      navigate(
        {
          hash: location.hash,
          search: stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
        },
        {
          replace: navigateMode === 'replace',
        },
      );
    }
  };

  return [targetQuery, useMemoizedFn(setState)] as const;
};

export default useUrlState;

要点分析

useLocation

useLocation返回表示当前页面的location对象。location对象包含有关当前 URL 的信息。
在该源码使用到的是hash和search信息。

  • hash:=从井号 (#) 开始的 URL(锚)。该源码中直接获取了当前url的hash值,并未改变。
  • search:=从问号 (?) 开始的 URL(查询部分),相当于query,也就是useUrlState重点处理的部分。

useUpdate

一个可以强制组件渲染的hook,用法极其简单, const update = useUpdate();然后update();即可。

parse, stringify

来自query-string库,作用是将对象和url的query(如‘?name=abc&class=2’)这两种格式进行互相转换。

useRef

useRef 返回一个可变的 ref 对象,其.current属性被初始化为传入的参数(initialValue),可以用来保存数据,返回的ref对象在组件的整个生命周期内持续存在。在这里用它来保存传入的初始值(以便当清除某个属性的时候,可以返回该属性的初始值)

参考资料:
https://cloud.tencent.com/dev...
https://react.docschina.org/d...


Non_
1 声望0 粉丝