博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
hook!
阅读量:5956 次
发布时间:2019-06-19

本文共 6519 字,大约阅读时间需要 21 分钟。

在react conf 2018上,react发布了一个新的提案hook。稳定的正式版可能要等一两个月之后才能出来,目前可以在v16.7.0-alpha上试用到rfc上各种提问。

那么这个hook到底是个什么呢,官方的定义是这样的

Hooks are a new feature proposal that lets you use state and other React features without writing a class.

这是一个比class更直观的新写法,在这个写法中react组件都是纯函数,没有生命周期函数,但可以像class一样拥有state,可以由effect触发生命周期更新,提供一种新的思路来写react。(虽然官方再三声明我们绝对没有要拿掉class的意思,但hook未来的目标是覆盖所有class的应用场景)

其实在看demo演示的时候我是十分抗拒的,没有生命周期函数的react是个什么黑魔法,虽然代码变得干净了不少,但写法实在是发生了很大的转变,有种脱离掌控的不安全感,我甚至有点怀疑我能不能好好debug。

演示的最后dan的结束语是这样的

hook代表了我们对react未来的愿景,也是我们用来推动react前进的方法。因此我们不会做大幅的重写,我们会让旧的class模式和新的hook模式共存,所以我们可以一起慢慢的接纳这个新的react。

我接触react已经四年了,第一次接触它的时候,我第一个想问的是,为什么要用jsx。第二个想问的是,为什么要用这个logo,毕竟我们又不是叫atom,也不是什么物理引擎。现在我想到了了一个解释,原子的类型和属性决定了事物的外观和表现,react也是一样的,你可以把界面划分为一个个独立的组件,这些组件(component)的类型(type)和属性(props)决定了最终界面的外观和表现。讽刺的是,原子一直被认为是不可分的,所以当科学家第一次发现原子的时候认为这就是最小的单元,直到后来在原子中发现了电子,实际上电子的运动更能决定原子能做什么。hook也是一样的,我不认为hook是一个新的react特性,相反的,我认为hook能让我更直观的了解react的基本特性像是state、context、生命周期。hook能更直观的代表react,它解释了组件内部是如何工作的,我认为它被遗落了四年,当你看到react的logo,可以看到电子一直环绕在那里,hook也是,它一直在这里。

于是我决定干了这杯安利。

试了几个比较基本的api写了几个demo,代码在 , 完全的api还请参考官方文档

api

基本的hook有三个

  • useState(相当于state)
  • useEffect(相当于componentDidUpdate, componentDidMount, componentWillUnmount)
  • useContext(相当于Context api)

useState

const [state, setState] = useState(initialState);

import { useState } from 'react';function Example() {  const [count, setCount] = useState(0);  return (    

You clicked {count} times

);}复制代码

在这里react组件就是一个简单的function

  • useState(initialState)也是一个函数,定义了一个state,初始值为initialState,返回值是一个数组,0为state的值,1为setState的方法。

  • 当state发生变化时,函数组件刷新。

  • 可以useState多次来定义多个state,react会根据调用顺序来判断。

你一定也写过一个庞大的class, 有一堆handler函数,因为要setState所以不能挪到组件外面去,然后render函数就被挤出了页面,每次想看render都要把页面滚到底下。

现在因为useState是函数,所以它可以被挪到组件外面,连带handler一起,下面是一个具体一点的表单例子。

import React, { useState } from 'react';// 表单组件,有name, phone两个输入框。export default () => {  const name = useSetValue('hello');  const phone = useSetValue('120');  return (    
);}// controlled input componentconst Item = ({ value, setValue }) => (
);// 可以将state连同handler function一起挪到组件外面。// 甚至可以export出去,让其他组件也能使用这个state逻辑const useSetValue = (initvalue) => { const [value, setValue] = useState(initvalue); const handleChange = (e) => { setValue(e.target.value); } return { value, setValue: handleChange, };}复制代码

useEffect

这个api可以让你在函数组件中使用副作用(use side effects),常见的会产生副作用的方式有获取数据,更新dom,绑定事件监听等,render只负责渲染,一般会等到dom加载好之后再去调用这些副作用方法。

useEffect(didUpdate/didMount);

useEffect(  () => {    const subscription = props.source.subscribe();    return () => {      subscription.unsubscribe();    };  },  [props.source],);复制代码

useEffect可以接受两个参数

  • 第一个参数为一个effect函数,effect函数在每次组件render之后被调用,相当于componentDidUpdate和componentDidMount两个生命周期之和。effect函数可以返回一个clear effect函数,会在下一次的effect函数执行之前执行,原来componentWillUnmount里执行的东西都可以交给它。调用顺序是:render(dom加载完成) => prevClearUseEffect => useEffect

  • 第二个参数是一个数组,只有当数组传入的值发生变化时,effect才会执行。

上面的写法如果用class实现的话应该是下面这样的。我们按时间先后将一个会产生副作用的函数的第1次调用、第2-n次调用、卸载分成3截,实际上它们总是一一对应出现的,应该是一个整体。

componentDidMount() {  this.subscription = props.source.subscribe();}componentDidUpdate() {  this.subscription = props.source.subscribe();}componentWillUnmount () {  subscription.unsubscribe();}复制代码

具体案例可以看一个轮播组件的demo

import React, { useState, useEffect } from 'react';import './index.css';const IMG_NUM = 3;export default () => {  const [index, setIndex] = useState(0);  const [isPlaying, setIsPlaying] = useState(false);  useEffect(() => {    // 每次组件刷新时触发effect, 相当cDM cDU    if (isPlaying) {      const timeout = setTimeout(() => {        // 改变state, 刷新组件        handleNext();      }, 2000);      // 返回清除effect的回调函数, 在每次effect调用完之后,如果有则执行      return () => clearTimeout(timeout);    }    // 如果不想每次render之后都调一次effect, 可以使用第二个参数作为筛选条件  }, [index, isPlaying]);  const handleNext = () => {    setIndex((index + 1) % IMG_NUM);  }  const handlePrev = () => {    setIndex((index - 1 + IMG_NUM) % IMG_NUM);  }  const handlePause = () => {    setIsPlaying(!isPlaying);  };  return (    
{index}
)}复制代码

useContext

const context = useContext(Context);

如果对react比较熟悉的话,应该用过Context这个api,用于在组件之间传递数据。useContext接受一个context对象(React.createContext生成),返回context.Consumer中获得的值。

export const Context = React.createContext(null);function Parent() {  const someValue = 'haha';  return (    
);}复制代码
function DeepChild() {  const someValue = useContext(Context);  return (
{someValue}
)}复制代码

16.7之前的Consumer写法是render props

function DeepChild() {  return (    
{ (someValue) =>
{someValue}
}
)}复制代码

似乎还能忍受,但是但是,为了避免不必要的刷新一般推荐用多个Context来传递刷新周期不同的数据,因此按原来的render-props写法很容易陷入多重嵌套地狱(wrapper-hell),很有可能你真正的渲染代码在十几个缩进后面才开始出现。继代码上下滚问题之后我们又出现了代码左右滚问题。

{ (value1) => (
{ (value2) => ( ... ) }
) }
// 我怎么还没有被同事打死?复制代码

useReducer

还有一堆高级hook

其中有一个useReducer

就是大家熟悉的那个redux里的reducer,来段模板代码让大家回忆一下。

const mapStateToProps = createStructuredSelector({	...});const mapDispatchToProps = (dispatch) => ({  ...});const withReducer = injectReducer({ ... });const withConnect = connect(mapStateToProps, mapDispatchToProps);export default compose(withReducer, withConnect)(Component);复制代码

以上的这些,使用了useReducer之后都没有了。

function Counter({initialCount}) {  const [state, dispatch] = useReducer(reducer, {count: initialCount});  return (    <>      Count: {state.count}                        );}复制代码

我还用useReducer实现了一个todo的demo,代码分了好几个文件就不放上来了

为什么要用hook

除了上面提到的,还有官方罗列出来的一些时常会在写class时遇到的麻烦

  • class组件间不能复用与state关联的代码,hook可以做到这一点。
  • 复杂而庞大的class组件很难被理解,hook能够让你把组件拆成更小的独立单元
  • 理解class是一件困难的事,无论是对人还是对开发工具而言都是这样。比如class里面的this指向的是组件,在箭头函数写法出来之前,我们不得不手动绑定this到调用函数的对象上。

总的来说

用react也好久了,工程越写越复杂,组件间的数据传递是一个很大的问题,从传统的传回调函数,到跨多层多组件共享数据的时候使用redux,后来嫌模板代码太多又自己封了一层render-props结果掉进wrapper嵌套地狱的坑里,Context出来的时候开心了一会儿然后发现依然在坑里。写是能写的,就是恐惧,每写一层,我的代码就又缩进了三个tab,离被同事打死又前进三步。

useContext,useReducer的用法让我想到了高阶,不同的是可以直接用变量接住而不是挂在props上,因此不用考虑props名冲突问题,但能达到高阶一层层包裹数据的效果。

从现有的文档来看,新的api非常的多,一些是我们熟悉的用法一些则是完全新的东西,且暂时还没能覆盖所有生命周期场景(比如getDeriveStateFromProps),但不着急,可以一步一步来。

hook正式版发布之后我还会来更新一次这个文档,在工程里正式使用一段时间之后会再更新一次,先奶一口。

参考

  1. 官方介绍hook的视频
  2. 官方文档
  3. 一些常见问题的官方解答

转载地址:http://pbgxx.baihongyu.com/

你可能感兴趣的文章
006android初级篇之jni数据类型映射
查看>>
Java 集合框架查阅技巧
查看>>
apache配置虚拟主机
查看>>
CollectionView水平和竖直瀑布流的实现
查看>>
前端知识复习一(css)
查看>>
spark集群启动步骤及web ui查看
查看>>
利用WCF改进文件流传输的三种方式
查看>>
Spring学习总结(2)——Spring的常用注解
查看>>
关于IT行业人员吃的都是青春饭?[透彻讲解]
查看>>
钱到用时方恨少(随记)
查看>>
mybatis主键返回的实现
查看>>
org.openqa.selenium.StaleElementReferenceException
查看>>
数论之 莫比乌斯函数
查看>>
linux下查找某个文件位置的方法
查看>>
python之MySQL学习——数据操作
查看>>
Harmonic Number (II)
查看>>
长连接、短连接、长轮询和WebSocket
查看>>
day30 模拟ssh远程执行命令
查看>>
做错的题目——给Array附加属性
查看>>
Url.Action取消字符转义
查看>>