最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 这篇文章教你如何根据业务封装自定义hooks

    正文概述 掘金(阿玉君_)   2021-02-23   769

    前言

    请注意:对React中一些重复的逻辑进行封装并不是封装公共的方法,而是一些组件内部重复的逻辑加以封装,使之可以重复使用。
    关于公共hooks的封装,阿里开源的ahook有不少可以使用的hook,当然也有关于对antd使用的hook,可是每个项目的逻辑或许只差一步,就无法使用。所以,开发人员必须掌握如何根据自己的业务来封装特定hook,才能使开发提效,代码优美,才能达到使用React-Hooks的目的。
    下面我会根据我自己系统的项目来举例说明,如何封装特定的hooks。

    实例

    这篇文章教你如何根据业务封装自定义hooks 上面是我司某项目一个经典列表页,具体逻辑为

    1. 搜索条件1、2、3、4互相联动,及搜索条件1影响搜索条件2,搜索条件2影响搜索条件3...以此类推。所以当搜索条件1选择值发生变化时,搜索条件2、3、4需要清空重新选择,以此类推...
    2. 当某个搜索条件值变化时,下边的table需要重新请求,更新数据,也就是搜索条件影响table的数据。

    这种逻辑的列表页在我司这个系统有很多,所以我把这段逻辑提成了两个Hooks。将搜索条件之间的限制提成一个hooks,将搜索条件与table的联动提成一个hooks

    自定义hooks

    整体页面代码

    const columns = [
      {
        title: '姓名',
        dataIndex: 'name',
      },
      {
        title: '年龄',
        dataIndex: 'age',
      },
      {
        title: '毕业院校',
        dataIndex: 'school',
      },
      {
        title: '所在单位',
        dataIndex: 'work',
      },
      {
        title: '家乡',
        dataIndex: 'home',
      },
      {
        title: '备注',
        dataIndex: 'command',
      },
    ];
    
    const dataSource = [
      {
        name: '张三',
        age: 29,
        school: '北大',
        work: '阿里巴巴',
        home: '北京',
        command:'暂无'
      },
      {
        name: '李四',
        age: 19,
        school: '',
        work: '',
        home: '北京',
        command:'暂无'
      },
      {
        name: '马武',
        age: 88,
        school: '北大',
        work: '阿里巴巴',
        home: '天津',
        command:'暂无'
      },
      {
        name: '赵六',
        age: 27,
        school: '北大',
        work: '百度',
        home: '伤害',
        command:'暂无'
      },
      {
        name: '整齐',
        age: 59,
        school: '五道口职业技术学院',
        work: '腾讯',
        home: '北京',
        command:'暂无'
      },
      {
        name: '老⑧',
        age: 59,
        school: '无',
        work: '腾讯',
        home: '北京',
        command:'奥利给'
      },
    ]
    
    const request = (url, param) => {
      const paramsLength = Object.values(param).filter(Boolean).length
      return new Promise(res => {
        setTimeout(() => {
          res({items:dataSource.slice(0,paramsLength),total:dataSource.length})
        },3000)
      })
    }
    const Page = () => {
      const [selectValue, setSelectValue] = useState({
        one:undefined,
        two: undefined,
        three:undefined,
        four:undefined,
      });
    
      const { onSelectChange } = useLimitSelect(selectValue, setSelectValue);
    
      const { loading, tableProps } = useTable('/api/fetch', selectValue, request);
    
      useEffect(() => {
      console.log(selectValue);
      }, Object.values(selectValue))
    
      return (
        <div className='page-one'>
          <Row  gutter={16}>
            <Col span={6}>
              <div>搜索条件1</div>
              <Select value={selectValue.one} onChange={onSelectChange('one')}>
                {[1, 2, 3].map((item) => (
                  <Option key={item} value={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Col>
            <Col span={6}>
              <div>搜索条件2</div>
              <Select value={selectValue.two} onChange={onSelectChange('two')}>
                {[4, 5, 6].map((item) => (
                  <Option key={item} value={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Col>
            <Col span={6}>
              <div>搜索条件3</div>
              <Select value={selectValue.three} onChange={onSelectChange('three')}>
                {[7, 8, 9].map((item) => (
                  <Option key={item} value={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Col>
            <Col span={6}>
              <div>搜索条件4</div>
              <Select value={selectValue.four} onChange={onSelectChange('four')}>
                {[10, 11, 12].map((item) => (
                  <Option key={item} value={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Col>
          </Row>
          <Table
            columns={columns}
            style={{ marginTop: '20px' }}
            loading={loading}
            {...tableProps}
          />
        </div>
      );
    };
    
    1. request为请求接口的函数,使用Promise和settimeout模拟一下
    2. selectValue中四个属性分别对应四个搜索条件值,初始化都为空值
    3. useLimitSelect为Select之间的自定义hooks、useTable为Select和Table联动的自定义hooks
    4. onSelectChange为Select下拉菜单onChange事件
    5. tableProps为Table组件需要的一系列Props

    useLimitSelect

    export const useLimitSelect = (value, setValue) => {
        const preValue = useRef(value);
        useEffect(() => {
            const preV = preValue.current;
            const keys = Object.keys(value);
            let change = false;
            const obj = {};
            for (let i = 0; i < keys.length; i++) {
                if (change) {
                    obj[keys[i]] = undefined;
                }
                if (preV[keys[i]] !== value[keys[i]]) {
                    change = true;
                }
            }
            setValue(pre => ({ ...pre, ...obj }));
            preValue.current = value;
        }, Object.values(value));
      
        const onSelectChange = useCallback((type) => (value) => {
            setValue(pre => ({ ...pre, [type]: value }));
        }, [])
        return { onSelectChange };
    };
    

    将selectValue和setSelectValue作为参数传入,useRef用于保存上一次的selectValue,每次value值发生变化,对value中的参数进行遍历,逐个比较,如果有一个发生了改变,那么从发生改变的下一个起,重置他们的值为undefined,保存到一个对象中,遍历完后统一更新value。
    这样,只要将需要依次联动的select的value值按顺序传入,只要一个值变化,后续的值都会重新置空,与此同时想要做一些别的操作,完全可以在组件内部通过useEffect进行监听,避免逻辑耦合。

    useFetch

    export const useFetch = (url,param,fetcher,options) => {
        const [loading, setLoading] = useState(false);
        const [data, setData] = useState(undefined);
        const [isError, setIsError] = useState(false);
    
        const request = useCallback( async () => {
            setLoading(true);
            try {
                const data = await fetcher(url, param)
                options.onSuccess && options.onSuccess(data, url, param);
                unstable_batchedUpdates(() => {
                    setData(data);
                })
            } catch (error) {
                setIsError(true)
                options.onError && options.onError(error, url, param)
            }
            setLoading(false);
        }, [fetcher,...Object.values(param),url])
        
        useEffect(() => {
            request();
        }, Object.values(param));
        return { data, loading, isError, request };
    }
    

    useFetch是为了统一处理请求我项目table数据接口封装的hooks。url为请求的接口、param为请求的参数、fetcher为请求的函数(比如组件内部模拟的request函数就是)、options是请求额外的处理函数,比如说请求成功需要做一些操作,请求失败需要做一些操作。只要是param参数变化,就需要重新请求table数据。

    useTable

    export const useTable = (url = '',param = {},fetcher,options = {}) => {
        const { defaultParams = { page:1, pageSize:15 }, onResponse = Response } = options;
        const [query, setQuery] = useState(() => ({ page: defaultParams.page, pageSize: defaultParams.pageSize }));
        const { data, loading, isError, request } = useFetch(url, { ...query, ...param }, fetcher, options);
        console.log(data);
        const onTableChange = useCallback((pagination) => {
            const { current, pageSize } = pagination;
            setQuery((prev) => ({ ...prev, current, pageSize }));
        },[])
    
        useEffect(() => {
            setQuery(prev => ({ ...prev, page: 1, pageSize: 15 }));
        }, Object.values(param));
    
        const refresh = useCallback(() => {
            setQuery((prev) => ({ ...prev, current: 1, pageSize: 15 }));
        }, [])
        
        const newData = onResponse ? onResponse(data) : data;
        return {
            tableProps: {
                onChange: onTableChange,
                dataSource: newData.data,
                pagination: {
                    total: newData.total, 
                    pageSize: query.pageSize,
                    current: query.current,
                    size: 'small',
                    position: ['bottomCenter'],
                }
            },
            loading,
            isError,
            request,
            refresh,
        }
    }:
    

    useTable的代码稍微多一些,但并不复杂。总共可以传入4个参数:

    1. url 请求table数据的URL
    2. param 请求table数据的参数
    3. fetcher 请求table数据的函数
    4. options 额外的参数,包括修改默认参数、数据返回数据处理等

    由于我这个系统table默认都是从第一页开始、每页15条数据,所以table的页码参数就默认为1、15。onResponse是预留出对请求来的数据做统一处理的函数,这个看自己项目的逻辑。
    将页码信息保存到一个state中是为table页码改变留出逻辑,同时搜索条件改变,需要重新请求第一页的数据。
    useTable中调用上边的useFetch,将页码信息和搜索条件信息一同传入就可以了。
    onResponse中对请求回来的数据单独做一些处理,比如说字段值更改之类的。

    最后

    以上只是本人针对自己的项目总结的部分自定义Hooks,其中每个Hook单独拿出来也可以直接使用。封装自定义Hooks一定要提炼出自己业务中那些公共的逻辑,并且每一个Hook只处理一段逻辑,避免耦合
    封装自定义Hooks不仅要求对逻辑抽象、提取有一定能力,同时也要求开发人员对自己项目整体业务走向有一定的掌控力,如果一个自定义Hook只能在一个地方被使用,那么就没有封装的必要了。
    多总结、多练习、愿读完这篇文章的developer都能成为技术大牛!


    起源地下载网 » 这篇文章教你如何根据业务封装自定义hooks

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元