React Hooks 状态的分层设计、自定义 hook

react-hooks 是 react16.8以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。

React Hooks是React框架内的逻辑复用形式,因其轻量、易编写的形态,必然会逐渐成为一种主流。但在实际的开发中,我依然觉得大部分的开发者对hook的使用过于粗暴,缺乏设计感和复用性。

在运用状态的过程中,颗粒化太细了,比如:table 组件

const [ loading, setLoading ] = useState(false);
const [ query, setQuery ] = useState({});
const [ dataSource, setDataSource ] = useState([]);
const [ pageOptions, setPageOptions ] = useState({
  page: 1,
  pageSize: 10,
  totalCount: 0
});

这样一但组件状态过多,就很难读懂组件的状态对应了,其实 React Hooks 的初终并不希望这样,弥补了无状态组件,由于用法过于粗暴,增加代码可读性。而是提高代码的可复用性、逻辑性,减少渲染次数,优化性能。

笔者认为在项目过程中应该用 Hooks Api 封装符合自己项目,自己业务的各类 Hooks。不是适用,像上面的用法,虽然达到的某种目的,相对 class 类组件,并没有多大改进,代码的可读性反而变差了。所以 React Hooks 正确运用姿势,是运用基状态的分层设计,创造属性自己*。废话不多说,下面我们一起来就 table 组件,封装自己的自定义 hooks,*。

自定义useTable设计思路

  1. 我们需要 state 来保存列表数据,总页码数,当前页面,loading 等信息
  2. 需要暴露一个方法用于,改变分页数据,从新请求数据
  • useTable
import { useState, useEffect, useMemo } from 'react';
import { Table, Pagination } from 'antd';

function useTable(query, callback) {
  const [ loading, setLoading ] = useState(false)
  const firstRequest = useRef(false);
  const [ pageOptions, setPageOptions ] = useState({
    page: 1,
    pageSize: 10
  });
  const [ tableData, setTableData ] = useState([]);

  const getList = useMemo(() => {
    return async payload => {
      if (!callback) return;
      
      setLoading(true);
      const data = await callback(payload || {...query, ...pageOptions})
      if (data.code === 0) {
        setTableData(data.data);
        firstRequest.current = true;
      }
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    firstRequest.current && getList({
      ...query,
      ...pageOptions
    })
  }, [pageOptions]);

  useEffect(() => {
    getList({
      ...query,
      ...pageOptions,
      page: 1
    })
  }, [query]);

  const handerChange = useMemo(() => (options) => setPageOptions({...options}), []);

  return [loading, tableData, handerChange, getList]
}

export default useTable;

使用方法:

import { useState } from 'react';
import useTable from './useTable';
import { Table, Pagination } from 'antd';


function CustomHook() {
  const jsonToString = (obj) => {
    let arr = [];
    for(let key in obj){
        arr.push(key + '=' + obj[key]);
    }
  
    return arr.join('&');
  }
  
  const getTableData = (payload) => {
    const query = jsonToString(payload);
  
    return fetch('http://127.0.0.1:3001/getList?' + query).then(res => res.json());
  }

  const [ query, setQuery ] = useState({});
  const [ loading, tableData, handerChange ] = useTable(query, getTableData);
  const { page, pageSize, totalCount, dataSource } = tableData;

  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: '住址',
      dataIndex: 'address',
      key: 'address',
    },
  ];

  return (
    <>
      <Table
        dataSource={dataSource}
        columns={columns}
        rowKey="id"
        loading={loading}
        pagination={false} />
      <Pagination
        defaultCurrent={page}
        total={totalCount}
        style={{float: 'right', marginTop: '20px'}}
        onChange={(page, pageSize) => handerChange({ page, pageSize })}
        onShowSizeChange={(current, size) => handerChange({ page: current, pageSize: size }) } />
    </>
  )
}

export default CustomHook;
  • useThrottle 节流
import { useState, useRef, useEffect } from 'react';

/**
 * @desc 节流
 * @param {*} fn 回调函数
 * @param {*} ms 时间
 * @param {*} deps 副作用,设置更新触发
 */
const useThrottle = (fn, ms = 30, deps = []) => {
  const previous = useRef(0);
  const [ time, setTime ] = useState(ms);

  useEffect(() => {
    const now = Date.now();
    
    if (now - previous.current > time) {
      fn();
      previous.current = now;
    }
  }, deps);

  const cancel = () => {
    setTime(0);
  }

  return [cancel];
}

export default useThrottle;

运用场景

import React, { useState } from 'react';
import useThrottle from '../useHooks/useThrottle';

const CustomInput = () => {
  const [ value, setValue ] = useState('');
  const handlerChange = (e) => {
    console.log(1, value)
  }

  const [ cancel ] = useThrottle(handlerChange, 50, [value])
  

  return (
    <input type="text" value={value} onChange={(e) => setValue(e.target.value)}/>
  )
}

export default CustomInput;
  • useDebounce 防抖
import { useRef, useEffect } from 'react';

/**
 * 防抖
 * @param {*} fn 回调函数
 * @param {*} ms 时间
 * @param {*} deps 副作用,设置更新触发
 * @returns 
 */
const useDebounce = (fn, ms = 30, deps = []) => {
  const timeout = useRef(0);

  useEffect(() => {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }

    timeout.current = setTimeout(() => {
      fn();
    }, ms);
  }, deps)

  const cancel = () => {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }
  }

  return [cancel]
}

export default useDebounce;

运用场景

import React, { useState } from 'react';
import useDebounce from '../useHooks/useDebounce';

const CustomInput = () => {
  const [ value, setValue ] = useState('');
  const handlerChange = (e) => {
    console.log(1, value)
  }

  const [ cancel ] = useDebounce(handlerChange, 50, [value])
  

  return (
    <input type="text" value={value} onChange={(e) => setValue(e.target.value)}/>
  )
}

export default CustomInput;

更新中…

原文:https://www.ifrontend.net/2021/06/react-hooks/

上一篇:远程调用jenkins


下一篇:react-hooks初探:useEffect入门使用场景