6个React Hook最佳实践技巧

6个React Hook最佳实践技巧
作者 | Nathan Sebhastian
译者 | 王强
策划 | 蔡芳芳

本文最初发布于 Medium 网站,经原作者授权由 InfoQ 中文站翻译并分享。

在过去,像状态和生命周期函数这样的 React 特性只适用于基于类的组件。基于函数的组件被称为哑(dumb)、瘦(skinny)或表示(presentational)组件,因为它们无法访问状态和生命周期函数。

但是自从 React Hooks 发布以来,基于函数的组件已升格为 React 的一等公民。它使函数组件能够以新的方式编写、重用和共享 React 代码。

在这篇文章中,我将分享 6 个关于 React Hooks 的技巧。你可以把它当作一份指南,在将 Hooks 实现到组件中时可以拿来参考。

遵守 Hooks 规则

这条规则看起来是句废话,但无论是新手还是经验丰富的 React 开发人员,都常常会忘记遵循 React Hooks 的规则。这些规则包括:

仅在顶级调用 Hooks

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

不要这样做:
if (name !== '') {
 useEffect(function persistForm() {
   localStorage.setItem('formData', name);
 });
}
相比之下,你应该这样做:
useEffect(function persistForm() {
  if (name !== '') {
    localStorage.setItem('formData', name);
  }
});

这条规则能确保每次渲染组件时都以相同的顺序调用 Hooks。这样一来,React 就能在多个 useState 和 useEffect 调用之间正确保留 Hooks 的状态。

仅从函数组件调用 Hooks

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

遵循这一条规则,可以确保组件中的所有状态逻辑在源代码中都能清晰可见。

使用 ESLint 的 React Hooks 插件

React 团队还创建了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,以帮助开发人员在自己的项目中以正确的方式编写 React Hooks。这个插件能够帮助你在尝试运行应用程序之前捕获并修复 Hooks 错误。

它有两条简单的规则:

  • react-hooks/rules-of-hooks

  • react-hooks/exhaustive-deps

第一条规则只是强制你的代码符合我在第一个技巧中说明的 Hooks 规则。第二个规则,exhaustive-deps 用于实施 useEffect 的规则:effect 函数中引用的每个值也应出现在依赖项数组中。

例如,下面这个 userInfo 组件会触发 exhaustive-deps 警告,因为 userId 变量在 useEffect 内部被引用,但未在依赖项数组中传递:
function UserInfo({userId}) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    getUser(userId).then(user => setUser(user))
  }, []) // no userId here
  return <div>User detail:</div>
}

尽管 exhaustive-deps 这条规则看起来很烦人,但它能帮助你避免由未列出的依赖项引发的错误。

以正确的顺序创建函数组件

当创建类组件时,遵循一定的顺序可以帮助你更好地维护和改进 React 应用程序代码。

首先调用构造器并启动状态。然后编写生命周期函数,接着编写与组件作业相关的所有函数。最后编写 render 方法:
const propTypes = {
  id: PropTypes.number.isRequired,
  url: PropTypes.string.isRequired,
  text: PropTypes.string,
};
const defaultProps = {
  text: 'Hello World',
};
class Link extends React.Component {
  static methodsAreOk() {
    return true;
  }
  constructor(props) {
    super(props)
    this.state = {
      user = null
    }
  }

  componentDidMount() {
    console.log('component did mount')
  }
  componentDidUpdate() {
    console.log('component did update')
  }
  componentWillUnmount() {
    console.log('component will unmount')
  }
  render() {
    return <a href={this.props.url}>{this.props.text}</a>
  }
}
Link.propTypes = propTypes
Link.defaultProps = defaultProps
export default Link
编写函数组件时并没有构造器和生命周期函数,因此你可能会犯糊涂,因为这种结构并不像类组件里那样是强制的:
function App() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    console.log("component is mounted");
  }, []);
  const [name, setName] = useState('');
  return <h1>React component order</h1>;
}

但就像类组件一样,为函数组件创建定义的结构能够改善项目的可读性。

建议先使用 useState Hook 声明状态变量,然后使用 useEffect Hook 编写订阅,接着编写与组件作业相关的其他函数。

最后,你得返回要由浏览器渲染的元素:
function App() {
  const [user, setUser] = useState(null);
  const [name, setName] = useState('');
  useEffect(() => {
    console.log("component is mounted");
  }, []);
  return <h1>React component order</h1>;
}

通过强制一种结构,可以让代码流在众多组件之间保持一致,看起来也比较亲切。

useState 的用法可以和类组件的状态完全一致,不只用于单个值
许多 useState 示例会向你展示如何通过声明多个变量来声明多个状态:
const [name, setName] = useState('John Doe');
const [email, setEmail] = useState('johndoe@email.com');
const [age, setAge] = useState(28);
但是 useState 实际上既可以处理数组也可以处理对象。你依旧可以将相关数据分组为一个状态变量,如以下示例所示:
const [user, setUser] = useState(
  { name: 'John Doe', email: 'john@email.com', age: 28 }
);
这里有一个警告。使用 useState 的更新函数更新状态时,以前的状态会替换为新状态。这与类组件的 this.setState 不同,后者的新类中,新状态会与旧状态合并:
const [user, setUser] = useState(
  { name: 'John', email: 'john@email.com', age: 28 }
);
setUser({ name: 'Nathan' });
// result { name: 'Nathan' }
为了保留以前的状态,你需要创建将当前状态值传递到自身中的回调函数来手动合并它。由于上面的示例已将 user 变量分配为状态值,因此可以将其传递给 setUser 函数,如下所示:
setUser((user) = > ({ ...user, name: 'Nathan' }));
// result is { name:'Nathan', email: 'john@email.com', age: 28 }

根据数据在应用程序生命周期中的变化情况,建议在各个值彼此独立时将状态拆分为多个变量。

但是对于某些情况,例如构建一个简单的表单,最好将状态分组在一起,以便更轻松地处理更改和提交数据。

简而言之,你需要在多个 useState 调用和单个 useState 调用之间保持平衡。

使用自定义 Hooks 共享应用程序逻辑

在构建应用程序时,你会注意到一些应用程序逻辑会在许多组件中一次又一次地使用。

随着 React Hooks 的发布,你可以将组件的逻辑提取到可重用的函数中作为自定义 Hooks,如我在以下文章中所展示的那样:

可扩展 React 项目的 6 个技巧和最佳实践:

https://blog.bitsrc.io/best-practices-and-tips-for-a-scalable-react-application-db708ae49227

你可以使用 Bit 之类的工具将 Hooks 发布到单个集合中,这样你就可以在不同的应用程序中安装和重用它们。它不需要你创建一个全新的“Hooks 库”项目,你可以一点点将新的 Hooks 从任何项目“推入”你的共享集合。

6个React Hook最佳实践技巧

针对这个方法,唯一要强调的是你不能在类组件中使用 Hooks。所以如果你的项目中还有老式的类组件,就需要将它们转换为函数,或者使用其他可重用逻辑模式(HOC 或渲染 Props)。

使用 useContext 避免 prop drilling

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

考虑以下示例:

https://bit.dev/nsebhastian/tutorial-examples/prop-drill-example?example=5f941e4445728c001924150a

从示例中可以看到,即使 Hello 组件不需要 props,App 组件也会通过 Hello 组件将 name props 传递给 Greeting 组件。

React Context 是一项功能,它提供了一种通过组件树向下传递数据的方法,这种方法无需在组件之间手动传 props。父组件中定义的 React Context 的值可由其子级通过 useContext Hook 访问。

在下面的示例中,我将 name 数据(而非 props)传递给 Context Provider,给代码做了重构:

https://bit.dev/nsebhastian/tutorial-examples/prop-drill-example?example=5f941fae45728c001924150e

App 的任何子组件都可以通过 useContext Hook 访问数据。可以从文档中了解有关 useContext Hook 的更多信息:

https://reactjs.org/docs/hooks-reference.html#usecontext

总结

React Hooks 是 React 库的重要补充,因为它允许你用独一无二的方式编写、重用和共享 React 代码。

随着 Hooks 开始改变开发人员编写 React 组件的方式,需要一套新的编写 React Hooks 的最佳实践,以便多个团队之间更轻松地开发和协作。

虽然本文肯定还有遗漏的内容,但我希望以上分享的技巧能多少帮助你在项目中以正确的方式编写 React Hooks。

延伸阅读

https://blog.bitsrc.io/best-practices-with-react-hooks-69d7e4af69a7

活动推荐

近日伴随着 Vue 3.0 RC 版本的发布,Vue 又被推到风口浪尖,它因其入门简单、易上手,一直深受广大前端工程师的喜爱。但是好多人能够熟练使用 Vue,却不清楚它背后的原理,没有亲手做过框架。现在机会来了,前手机前端淘宝负责人 winter 手把手带你了解一个 Toy Vue 框架搭建的全过程,帮你掌握 Vue 框架背后的原理及实现方式,最后你可以亲自实现一个 Toy Vue 框架!扫码免费领取课程及源码哦👇

6个React Hook最佳实践技巧

点击阅读原文,了解更多课程信息👇

发表评论

邮箱地址不会被公开。 必填项已用*标注

相关