具有React Hooks的功能组件。 他们为什么更好?

相对较新的React.js版本16.8已发布,通过它可以使用钩子。 钩子的概念使您可以使用React的所有功能来编写成熟的功能组件,并且比起使用类,您可以通过多种方式更方便地完成此操作。


许多人都对钩子的出现表示了批评,在本文中,我想谈谈带钩子的功能组件给我们带来的一些重要优势,以及为什么我们应该转向它们。


我不会刻意研究使用钩子的细节。 这对于理解本文中的示例不是很重要,对React工作的一般理解就足够了。 如果您想确切地阅读此主题,有关钩子的信息在文档中 ,并且如果这个主题很有趣,我将写一篇更详细的文章,介绍何时正确使用钩子以及如何正确使用钩子。


挂钩使代码重用更加容易


让我们想象一个呈现简单形状的组件。 可以简单地输出一些输入并允许我们对其进行编辑的东西。


像这样的东西,如果大大简化,该组件将看起来像一个类:


class Form extends React.Component { state = { //   fields: {}, }; render() { return ( <form> {/*    */} </form> ); }; } 

现在想象一下,我们想在字段值更改时自动保存它们。 我建议省略其他函数的声明,例如shallowEqualshallowEqual


 class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { //   fields: {}, // ,       draft: { isSaving: false, lastSaved: null, }, }; saveToDraft = (data) => { if (this.state.isSaving) { return; } this.setState({ isSaving: true, }); makeSomeAPICall().then(() => { this.setState({ isSaving: false, lastSaved: new Date(), }) }); } componentDidUpdate(prevProps, prevState) { if (!shallowEqual(prevState.fields, this.state.fields)) { this.saveToDraft(this.state.fields); } } render() { return ( <form> {/*    ,     */} {/*    */} </form> ); }; } 

相同的示例,但带有钩子:


 const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

我们看到,差异还不是很大。 我们将useState更改useState挂钩,并导致保存草稿不是在componentDidUpdate ,而是在使用useEffect挂钩渲染组件之后。


我想在这里显示的区别(还有其他一些,我们将在下面讨论):我们可以获取此代码并在其他地方使用它:


 //  useDraft       const useDraft = (fields) => { const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return [draftIsSaving, draftLastSaved]; } const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

现在,我们可以使用刚才在其他组件中编写的useDraft挂钩! 当然,这是一个非常简化的示例,但是重用相同的功能是非常有用的功能。


钩子使您可以编写更直观的代码。


想象一个组件(现在以类的形式),例如,它显示当前的聊天窗口,可能的收件人列表以及用于发送消息的表单。 像这样:


 class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${this.state.currentChat} `); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; } 

该示例是有条件的,但非常适合演示。 想象一下这些用户操作:


  • 开启即时通讯1
  • 发送一条消息(假设请求花费很长时间)
  • 开启即时通讯2
  • 接收有关成功发送的消息:
    • “聊天消息已发送2”

但是消息已发送到聊天室1? 发生这种情况的原因是,类方法不适用于发送时的值,但不适用于请求完成时的值。 在这种简单情况下,这不是问题,但是首先纠正这种行为将需要额外的照顾和额外的处理,其次,它可能是错误的来源。


对于功能组件,其行为是不同的:


 const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${currentChat} `); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; } 

想象一下相同的用户操作:


  • 开启即时通讯1
  • 发送一条消息(请求又花了很长时间)
  • 开启即时通讯2
  • 接收有关成功发送的消息:
    • “聊天消息已发送1”

那么,发生了什么变化? 发生的变化是,现在对于currentChat不同的每个渲染,我们正在创建一个新方法。 这使我们完全不用考虑将来是否会发生某些变化-我们正在努力处理现在的情况每个渲染组件都会自行关闭与其相关的所有内容


钩子使我们摆脱了生命周期


此项与上一项重叠。 React是一个用于声明性描述接口的库。 可声明性极大地方便了组件的编写和支持,使您无需考虑如果不使用React便必须执行的操作。


尽管如此,在使用类时,我们仍面临着组件生命周期。 如果不深入,它看起来像这样:


  • 组件安装
  • 组件更新(更改stateprops
  • 组件拆卸

看起来很方便,但我坚信这完全是因为习惯。 这种方法不像React。


相反,带有钩子的功能组件使我们可以编写组件,而不是考虑生命周期,而是考虑同步 。 我们编写函数,以便其结果根据外部参数和内部状态唯一地反映接口的状态。


useEffect实际上是为另一个而useEffect ,它被认为是componentDidMountcomponentDidUpdate的直接替代。 在使用它时,我们会告诉您这样的反应:“渲染后,请执行以下效果。”


这是一个很好的示例,展示了组件如何与来自useEffect的大型文章中的点击计数器一起使用


  • 反应:告诉我在这种状态下要渲染什么。
  • 您的组件:
    • 这是渲染结果: <p> 0 </p>
    • 并且,请在完成以下操作时执行此效果: () => { document.title = ' 0 ' }
  • 反应:好的。 更新界面。 嘿,浏览器,我正在更新DOM
  • 浏览器:很好,我画了。
  • React:超级,现在我将调用从组件收到的效果。
    • 它以() => { document.title = ' 0 ' }

更具说明性,不是吗?


总结


React Hooks使我们摆脱了一些问题并促进了组件的理解和编码。 您只需要更改我们应用于他们的思维模型即可。 功能组件本质上是参数的接口功能。 他们描述了在任何给定时间应有的一切,并帮助他们不要思考如何应对变化。


是的,有时您需要学习如何正确使用它们,但是以同样的方式,我们没有立即学习如何以类的形式使用组件。

Source: https://habr.com/ru/post/zh-CN443488/


All Articles