React Hooks指南

引入

Hook 是 React 16.8 的新增特性,由于目前前端的 React 项目版本都高于此版本,因此可以直接在项目中使用。

你也可以基于 React 提供的 hook 开发和封装更加通用和高级的自定义 hook。

自定义 Hook
模拟类组件生命周期

useMount

import { useEffect } from 'react';
const useMount = fn => {
	useEffect(() => {
    if (typeof fn === 'function') {
      fn()
    }
  }, []);
}

useUnmount

import { useEffect } from 'react';
const useUnmount = fn => {
	useEffect(() => () => {
    fn();
  }, []);
}

useUpdate

import { useEffect, useRef } from 'react';
const useUpdate = (fn, deps) => {
	const isMounted = useRef(false);
  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return fn();
    }
  }, deps);
};

从Redux批量获取model

useConnect
import { useSelector } from 'umi';
export default function useConnect(models = []) {
    return useSelector(state => {
        const modelMap = {};
        models.forEach(model => {
            if (model in state) {
                modelMap[model] = state[model];
            }
        });
        return modelMap;
    });
}

管理定时器

import { useEffect, useRef } from "react";

const useInterval = (fn: Function, timestamp = 300) => {
  const fnRef = useRef<Function>();
  fnRef.current = fn;
  useEffect(() => {
    const timer = setInterval(() => {
      fnRef.current?.();
    }, timestamp);
    return () => {
      clearInterval(timer);
    };
  }, [timestamp]);
};

export default useInterval;

第三方Hook库

按照第三方库的官方文档下载使用。

例如 ahooks

// 安装依赖
npm i ahooks --save

// 使用 Hooks
import { useRequest } from 'ahooks';

规范

[强制] 仅从函数组件调用 Hooks 。

不要从常规 JavaScript 函数中调用 Hooks。仅从函数组件或自定义 Hooks 中调用 Hooks。

[强制] 仅在顶级调用 Hooks 。

不要在循环、条件和嵌套函数内调用 Hooks。当你想有条件地使用某些 Hooks 时,请在这些 Hooks 中写入条件。

// bad
if (name !== '') {
 useEffect(function persistForm() {
   localStorage.setItem('formData', name);
 });
}

// good
useEffect(function persistForm() {
  if (name !== '') {
    localStorage.setItem('formData', name);
  }
});

[强制] 自定义 Hook 的函数名必须以 use 开头。

// bad
const customBoolean = () => {}

// good
const useBoolean = () => {}

[强制] 函数式组件使用函数表达式配合箭头函数进行声明。

// bad
function FunctionalComponent(props) {
  return <h1>This is a functional component.</h1>;
}
const FunctionalComponent = function(props) {
  return <h1>This is a functional component.</h1>;
}

// good
const FunctionalComponent = (props) => {
  return <h1>This is a functional component.</h1>;
};

[建议] 先使用 useState Hook 声明状态变量,然后使用 useEffect Hook 编写订阅,接着编写与组件作业相关的其他函数。最后,你得返回要由浏览器渲染的元素

[建议] 复杂的状态保存到 Redux 中。

function App() {  
  const [user, setUser] = useState(null);  
  const [name, setName] = useState('');  
  useEffect(() => {    
    console.log("component is mounted");  
  }, []);  
  return <div>React component order</div>;
}

[建议] 局部状态不推荐使用 useReducer 。

会导致函数内部状态过于复杂,难以阅读。 useReducer 建议在多组件间通信时,结合 useContext 一起使用。

[建议] 使用 useContext 避免 prop drilling。

prop-drilling 是 React 应用程序中的常见问题,指的是将数据从一个父组件向下传递,经过各层组,直到到达指定的子组件,而其他嵌套组件实际上并不需要它们。

[建议] 减少在 函数组件内部声明函数。

函数式组件在每一次渲染的过程中都会生成独立的所用域,因此,在组件内部的子函数和变量等在每次渲染的时候都会重新生成。

// bad
function App() {
  const [counter, setCounter] = useState(0);
  function formatCounter(counterVal) {
    return `The counter value is ${counterVal}`;
  }
  return (
    <div className="App">
      <div>{formatCounter(counter)}</div>
      <button onClick={() => setCounter(prevState => ++prevState)}>
        Increment
      </button>
    </div>
  );
}

// good
function formatCounter(counterVal) {
  return `The counter value is ${counterVal}`;
}
function App() {
 const [counter, setCounter] = useState(0);
 return (
   <div className="App">
     <div>{formatCounter(counter)}</div>
     <button onClick={()=>onClick(setCounter)}>
       Increment
     </button>
   </div>
 );

[建议] 使用 useCallback 包裹很少变化的函数,使用 useMemo 包裹昂贵的计算。

// good
function App(props) {
  const [counter, setCounter] = useState(0);
  const result = useMemo(() => {
    // 一些昂贵的计算操作
    // ...
    return ...
  }, []);
  const onClick = useCallback(()=>{
	  // 依赖少或者依赖项很少改变的函数
    // ...
  },[props.count]);
  return (
    <div className="App">
      <div>{formatCounter(counter)}</div>
      <button onClick={onClick}>
        Increment
      </button>
    </div>
  );
}

[建议] 对于不需要重复初始化的对象使用 useRef 存储。

为什么有时候会出现无限重复请求的问题?

你可能使用了错误的依赖。

这个通常发生于你在effect里做数据请求并且没有设置effect依赖参数的情况。没有设置依赖,effect会在每次渲染后执行一次,然后在effect中更新了状态引起渲染并再次触发effect。无限循环的发生也可能是因为你设置的依赖总是会改变。你可以通过一个一个移除的方式排查出哪个依赖导致了问题。但是,移除你使用的依赖(或者盲目地使用[])通常是一种错误的解决方式。你应该做的是解决问题的根源。举个例子,函数可能会导致这个问题,你可以把它们放到effect里,或者提到组件外面,或者用useCallback包一层。useMemo 可以做类似的事情以避免重复生成对象。

为什么有时候在effect里拿到的是旧的state或prop呢?

可能的原因:

你的依赖列表可能缺失了某些依赖。

这种情况,只需要检查 effect 并添加正确的依赖。

你在异步操作中获取了state或prop。

通过 useRef 保存需要在异步操作中保存的变量。

组件内部的任何函数,包括事件处理函数和 effect,都是从它被创建的那次渲染中被「看到」的。

当state为数组或者对象时,如何更新?

Hook 更新状态与类组件的 setState 的方式不同,后者是合并,而前者是替换,因此,你需要手动合并需要更新的对象或者数组,然后更新它。

const [user, setUser] = useState({id: 1, name: 'Bob'});

useEffect(() => {
  getUser.then((res) => {
    setUser({...user, name: res.name});  // 合并新值到旧对象中
  })
}, [user]);

然而,React 推荐把 state 切分成多个 state 变量,每个变量包含的不同值会在同时发生变化。

如何正确地在useEffect里请求数据?[]又是什么?

[]表示effect没有使用任何React数据流里的值,因此该effect仅被调用一次是安全的。[]同样也是一类常见问题的来源,也即你以为没使用数据流里的值但其实使用了。你需要学习一些策略(主要是useReducer 和 useCallback)来移除这些effect依赖,而不是错误地忽略它们。
如何使用React Hooks获取数据?

我应该把函数当做effect的依赖吗?

尽量将函数和 effect 解耦。

在 effect 内部去声明它所需要的函数。
尝试把那个函数移动到你的组件之外。
在 effect 之外调用它。
把函数加入 effect 的依赖但把它的定义包裹进 useCallback。

依赖数组是如何比较?

Object.is 。

参考 useEffect 完整指南

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值