React Hook项目总结

179 阅读3分钟

1. 在FunctionComponent组件中使用debounce

error:

const FC = () => {
 // debounce函数
 const fetchAddress = debounce(value => {
  fetchApi(value).then(() => {
	// do something
  });
}, 700);
 return <Input onChange={(e) => { fetchAddress(e.target.value) }} />
}

right:

const FC = () => {
 // debounce函数
 const fetchAddress = useCallback(debounce(value => {
  fetchApi(value).then(() => {
	// do something
  });
}, 700), []);
 return <Input onChange={(e) => { fetchAddress(e.target.value) }} />
}

需要用usecallback缓存起来,不然每次渲染后debounce都是一个新的函数,没有防抖的效果;

2. 每轮渲染,函数组件都拥有其独立的状态

example1:

function Example() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count); // 输出的是?
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

可用useRef保存上一轮的状态,因为useRef会在每次渲染时返回同一个ref对象,类似于class组件的实例属性(this.xxx):

function Example() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + prevCountRef.current); // 输出的是?
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => {
      	setCount(count + 1)
        prevCountRef.current = count
        }}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

example2:

const FC = ({ getAFn }) => {
  const [a, setA] = useState(0);
  const getA = () => a;
  useEffect(() => {
    getAFn(getA);
  }, []);

  return (
    <p onClick={() => {
        setA(a + 1);
      }}>
      click1
    </p>
  );
};

class A extends React.Component {
  render() {
    return (
      <div>
        <FC getAFn={(fn) => (this.fn = fn)} />
        <span
          onClick={() => {
            console.log(this.fn()); // 输出多少?
          }}
        >
          click2
        </span>
      </div>
    );
  }
}

export default A;

需要去掉useEffect的依赖,保证每轮渲染都抛出属于该轮的函数状态,下面是能得到正确结果的写法:

const FC = ({ getAFn }) => {
  const [a, setA] = useState(0);
  const getA = () => a;
  useEffect(() => {
    getAFn(getA);
  });

  return (
    <p onClick={() => {
        setA(a + 1);
      }}>
      click1
    </p>
  );
};

class A extends React.Component {
  render() {
    return (
      <div>
        <FC getAFn={(fn) => (this.fn = fn)} />
        <span
          onClick={() => {
            console.log(this.fn()); // 输出多少?
          }}
        >
          click2
        </span>
      </div>
    );
  }
}

export default A;

3.自定义Hook

1.处理antd table数据

自定义tablehook,脱离UI提取表格数据交互逻辑:

import { useReducer, useCallback, useEffect } from "react";
const defaultSearchParams = {};

// 抽取出表格可以共用的状态
const useTableHook = ({
  queryApi,          // 请求的接口
  dataKey = "list",  //返回数据列表的Key
  totalKey = "total", // 返回数据总数的Key
  pageIndexKey = "page", // 请求参数key
  pageSizeKey = "size", // 请求参数key
  searchParams = defaultSearchParams, //搜索参数,不能直接写searchParams = {}
  callBack           // 请求完成的回调
}) => {
  const [tableState, dispatch] = useReducer((state, action) => {
    const { type, payLoad } = action;
    switch (type) {
      case "setLoading":
        return { ...state, loading: payLoad };
      case "setPageIndex":
        return { ...state, pageIndex: payLoad };
      case "setPageSize":
        return { ...state, pageSize: payLoad };
      case "setTotal":
        return { ...state, total: payLoad };
      case "setList":
        return { ...state, list: payLoad };
    
      default:
        return { state };
    }
  }, {
    loading: false,
    pageIndex: 1,
    pageSize: 10,
    total: 0,
    list: []
  });

  const { pageIndex, pageSize, total } = tableState;
  
  // 翻页配置
  const pagination = {
    current: pageIndex,
    pageSize,
    total,
    onChange: pageIndex => {
      dispatch({
        type: "setPageIndex",
        payLoad: pageIndex
      });
    },
    onShowSizeChange: (page, pageSize) => {
      dispatch({
        type: "setPageIndex",
        payLoad: 1
      });
      dispatch({
        type: "setPageSize",
        payLoad: pageSize
      });
    }
  };

  // 请求数据
  const queryTable = useCallback(param => {
    dispatch({ type: "setLoading", payLoad: true });
    queryApi(param).then(({ data }) => {
      if(data) {
        dispatch({ type: "setList", payLoad: data[dataKey] || [] });
        dispatch({ type: "setTotal", payLoad: data[totalKey] });
        callBack && callBack();
      }
    }).finally(() => {
      dispatch({ type: "setLoading", payLoad: false });
    });
  }, []);


  useEffect(() => {
    queryTable({ 
      [pageIndexKey]: pageIndex, [pageSizeKey]: pageSize, ...searchParams
    });
  }, [pageIndex, pageSize, searchParams]);
  
  return { tableState, dispatch, pagination, queryTable };
};

export default useTableHook;

在有表格的页面使用:

const TabelPage = () => {
  const [params, setParams] = useState({});
  const columns = [{
    title: "模板ID",
    dataIndex: "target",
    align: "center",
  }, ...];

  const { tableState, dispatch, pagination } = useTableHook({
    queryApi: api,
    searchParams: params
  });

  const { loading, list } = tableState;

  return (
    <>
      <Search doQuery={(params, type) => {
         if(type === "reset") {
          dispatch({ type: "setPageIndex", payLoad: 1});
          dispatch({ type: "setPageSize", payLoad: 10});
         }
          setParams(params);
      }} />
      <Table
        dataSource={list}
        loading={loading}
        columns={columns}
        pagination={pagination}
      />
    </>
  );
};

这里我使用的是什么时候使用useReducer管理数据状态,那什么时候使用useState,什么时候使用useReducer呢?

  • useState也是useReducer的封装,使用后的效果都是一样的;
  • 当一个组件涉及的状态较多的时候,例如state是Object, Array类型,可以使用useReducer组织;
  • 当state是一个简单的String, Number的时候,可使用useState;
  • 如果每次修改,object的每个属性值都会一起改变,例如鼠标移动监听xy坐标{x, y},也可使用useState;

2.处理弹窗显示关闭交互逻辑

定义modalHook:

import React, { useState } from "react";
import { Modal } from "antd";

const useModal = ({
  modalProps, content
}) => {
  const [visible, setVisible] = useState(false);
  const CustomModal =  () => {
      return (
          <Modal
            visible={visible}
            maskClosable={false}
            onCancel={() => {
              setVisible(false);
            }}
            {...modalProps}
          >
              {content}
          </Modal>
      );
  };
  return {
    setVisible, CustomModal
  };
};

export default useModal;

使用:

import React, { useEffect, useCallback } from "react";
import useModalHook from "@/tools/useModalHook";

const Com = () => {

  const onOk = useCallback(() => {
    
  }, []);

  const { setVisible, CustomModal } = useModalHook({
    content: <div>弹窗内容</div>,
    modalProps: {
      title: "弹窗",
      width: 680,
      onOk
    }
  });

  return <>
  		  <button onClick={() => { setVisible(true) }}>显示弹窗</button>
  		  <CustomModal />
         </>
};

export default Com;

当然网上也有很多方便的自定义hook可以用,推荐阿里的ahooks