React Hooks 实用指南

前言

React Conf 2018会议中,Dan Abramov 介绍了 React Hooks。官方的描述为html

Hook是一项新功能提案,可以让您在不编写类的状况下使用状态和其余React功能。 它们目前处于React v16.7.0-alpha中。计划将在 2019 Q1 推出到主版本中。前端

痛点

如下是React Hooks功能的动机,它解决了现有React中的一些问题react

组件之间很难共享状态

React没有一种将可重用的行为附加到组件的方法(例如连接到store)。若是您使用过一段时间,您可能会使用render propsheight-order components组件解决这个问题。可是这些模式要求您再使用他们时重构组件,这会很麻烦。若是您使用React DevTools看一下您的程序,您会发现您的组件被各类组件所包裹这叫作包装地域,好比:providers、comsumers、higher-order components、render props等。这里有一个更深层的根本问题:React须要一个更好的方法来共享状态逻辑。git

这就是Hooks,您能够从组件中提取有状态逻辑,以即可以独立测试和重用。Hooks容许您不更改组件层次结构的状况下重用有状态逻辑。这样就能够轻松在多组件之间或与社区共享Hooksgithub

组件愈来愈复杂,变得难以理解

咱们常常不得不维护一些组件,这些组件一开始很简单,随着时间的延伸组件发展成一堆没法管理的有状态逻辑和一些反作用。每一个生命周期方法常常包含不相关的逻辑组合。举个例子,组件可能会在componentDidMountcomponentDidUpdate中拉取一些数据。还有componentDidMount方法可能还包含一些事件监听的不相关逻辑,而且再componentWillUnmount`中卸载监听。可是彻底不相关的代码会合并到一个方法中。是很容易引发bug和不一致性。redux

在不少状况下,不能将这些组件拆分红更小的组件由于逻辑遍及许多地方。对它们进行测试也很困难。这正是不少人将React和状态管理库结合使用的缘由。可是这更容易建立更多的抽象,要求您在许多不一样的文件之间跳转,重用组件将变得更加困难。api

为了解决这个问题,Hooks容许您根据相关的功能将他们拆分为一个更小的函数。而不是强制基于声明周期函数进行拆分。您还能够选择使用reducer管理组件的本地状态,使其更具可预测性。数组

类让人和机器都混淆

除了使代码重用和代码组织更加困难外,咱们发现类(classes)可能成为学习React的一大障碍。您必须了解它在JavaScript中是如何工做的,这与它在大多数语言中的工做方式有很大不一样。您必须明白如何正确的绑定事件处理和还没稳定的新语法,代码很是冗长。你们可能很容易就会明白属性(props)、状态(state)、从上往下的数据流(top-down data flow)但类(classes)就很难理解。React中的函数和类组件之间的区别以及什么时候使用每一个组件致使即便在经验丰富的React开发人员之间也存在分歧。使用函数可使用prepack更好的优化代码。可是使用类组件不能获得更好的优化。bash

为了解决这些问题,Hooks 容许您在没有类的状况下使用更多的React功能。服务器

useState

useState可让您的函数组件也具有类组件的state功能

使用语法以下:

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

useState返回一个数组,一个是state的值,第二个是更新state的函数

在真实的程序中咱们能够这样使用:

function TestUseState() {
  const [count, setCount] = React.useState(0);

  return (
    <div> <p>useState api</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
复制代码

使用 useState 须要注意一个事项,当你初始化是一个对象时。使用 setCount 时它不像类组件的 this.setState 会自动合并到 state 中。setCount 会使用当前的值覆盖以前的 state。以下所示

function TestUseStateObject() {
  const [state, setState] = React.useState({
    count: 0,
    greeting: "Hello, World!",
  });
  const handleAdd = () => {
    setState({
      count: state.count + 1
    })
  }
  console.log('state > ', state)
  return (
    <div> <p>useStateObject api</p> <p>Count: {state.count} <button onClick={handleAdd}>自增</button></p> </div>
  )
}
复制代码

1543423038609

咱们能够看到,当点击按钮时 state 被替换成了 {count: 1}。若是想要在 state 中使用一个对象须要在更新值的时候把以前的值解构出来,以下所示:

setState({
      ...state,
      count: state.count + 1
    })
复制代码

在函数中使用多个 state

function TestMultipleUseState() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('john');
  return (
    <div> <p>useState api</p> <p>Count: {count} - Name: {name}</p> </div>
  )
}
复制代码

如须要在线测试请前往codepen useState

useEffect

默认状况下 useEffect 在完成渲染后运行,咱们能够在这里获取DOM和处理其余反作用。但它还有两种不一样的运行阶段稍候我会解释。

function TestUseEffect() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log(`组件被更新,Count: ${count}`);
  });
  
  return (
    <div> <p>useEffect api</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
复制代码

上面的 useEffect 在每次组件渲染后运行,每当咱们点击自增按钮都会执行一次。

可是若是上面的代码在每次渲染后都执行,若是咱们在 useEffect 从服务器拉取数据。形成的结果就是每次渲染后都会从服务器拉取数据。或者是只有某些 props 被更新后才想执行 useEffect。那么默认的 useEffect 就不是咱们想要执行方式,这时 useEffect 提供了第二个参数。

useEffect(didUpdate, [])

useEffect第二个参数为一个数组。当咱们提供第二个参数时,只有第二个参数被更改 useEffect 才会执行。利用第二个参数咱们能够模拟出类组件的 componentDidMount 生命周期函数

function TestUseEffectListener() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log('componentDidMount fetch Data...');
  }, []);
  
  return (
    <div> <p>TestUseEffectListener</p> <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p> </div>
  )
}
复制代码

上面的代码中 useEffect 只会执行一次,当您点击自增 useEffect 也不会再次执行。

useEffect 第一个参数的函数中咱们能够返回一个函数用于执行清理功能,它会在ui组件被清理以前执行,结合上面所学的知识使用 useEffect 模拟 componentWillUnmount 生命周期函数

function TestUseEffectUnMount() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    return () => {
      console.log('componentUnmount cleanup...');
    }
  }, []);
  
  return (
    <div> <p>TestUseEffectUnMount</p> </div>
  )
}
复制代码

上面的代码中,当组件 TestUseEffectUnMount 将要销毁时会,会执行 console.log('componentUnmount cleanup...') 代码

如须要在线测试请前往codepen useEffect

useContext

useContext 可让您在函数中使用 context,它有效的解决了之前 ProviderConsumer 须要额外包装组件的问题

使用语法以下:

const context = useContext(Context);

如今让咱们来看看实际应用中这个 useContext 是如何使用的,代码以下:

function TestFuncContext() {
  const context = React.useContext(ThemeContext);

  return (
    <div style={context}>TestFuncContext</div>
  )
}
复制代码

咱们能够看到上面直接使用 React.useContext(ThemeContext) 就能够得到 context,而在以前的版本中须要像这样才能获取 <Consumer>({vlaue} => {})</Consumer> ,这极大的简化了代码的书写。

// 以前Consumer的访问方式
function TestNativeContext() {
  return (
    <ThemeContext.Consumer> {(value) => { return ( <div style={value}>TestNativeContext</div> ) }} </ThemeContext.Consumer> ); } 复制代码

如须要在线测试请前往codepen useContext

useReducer

useReduceruseState 的代提方案。当你有一些更负责的数据时可使用它。

使用语法以下:

const [state, dispatch] = useReducer(reducer, initialState)

第一个参数是一个 reduce 用来处理到来的 action,函数申明为:(state, action) => ()。第二个参数是一个初始化的state常量。

在返回值 [state, dispatch] 中,state 就是你的数据。dispatch 能够发起一个 action 到 reducer 中处理。

这个功能给个人感受就是组件本地的redux,感受仍是不错。在设计一些复杂的数据结构是可使用

如今让咱们来看看实际应用中这个 useReducer 是如何使用的,代码以下:

function TestUseReducer() {
  const [state, setState] = React.useReducer((state, action) => {
    switch(action.type) {
      case 'update':
        return {name: action.payload}
      default:
        return state;
    }
  }, {name: ''});
  
  const handleNameChange = (e) => {
    setState({type: 'update', payload: e.target.value})
  }
  return (
    <div> <p>你好:{state.name}</p> <input onChange={handleNameChange} /> </div> ) } 复制代码

当改变 input 中的值时会同时更新 state 中的数据,而后显示在界面上

如须要在线测试请前往codepen useReducer

useCallback

useCallbackuseMemo 有些类似。它接收一个内联函数和一个数组,它返回的是一个记忆化版本的函数。

使用语法以下:

const memoizedValue = useMemo(() => computeExpensiveValue(a), [a])

useCallback 的第一个参数是一个函数用来执行一些操做和计算。第二个参数是一个数组,当这个数组里面的值改变时 useMemo 会从新执行更新这个匿名函数里面引用到 a 的值。这样描述可能有点不太好理解,下面看一个例子:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些计算
      return num;
    },
    [],
  );
  console.log('记忆 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div> <p>TestUseCallback</p> </div>
  )
}
复制代码

_6d371a9a-47a9-4a5c-ac22-28a456b4d4d5

若是咱们想监听 num 值的更新从新作一些操做和计算,咱们能够给第二个参数放入 num 值,像下面这样:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些计算
      return num;
    },
    [num],
  );
  console.log('记忆 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div> <p>TestUseCallback</p> </div>
  )
}
复制代码

如须要在线测试请前往codepen useCallback

useRef

我以为 useRef 的功能有点像类属性,或者说您想要在组件中记录一些值,而且这些值在稍后能够更改。

使用语法以下:

const refContainer = useRef(initialValue)

useRef 返回一个可变的对象,对象的 current 属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。

一个保存input元素,并使其获取焦点程序,代码以下:

function TestUseRef() {
  const inputEl = React.useRef(null);
  const onButtonClick = () => {
    // 点击按钮会设置input获取焦点
    inputEl.current.focus(); // 设置useRef返回对象的值
  };
  
  return (
    <div> <p>TestUseRef</p> <div> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>input聚焦</button> </div> </div> ) } 复制代码

useRef 返回的对象您能够在其余地方设置好比: useEffect、useCallback等

如须要在线测试请前往codepen useRef

原文连接

感谢阅读 🙏

最后作一个广告,我建立了一个前端周刊每周五发布最新的技术文章和开源项目欢迎订阅

相关文章
相关标签/搜索