React Hooks

Hooks

Hooks是React v16.7.0-alpha的新特新,可以与react state相结合, 使函数组件功能类似于class一样。但是在Class类内部,不可以使用hooks.
React 提供了几个像useState,useEffect, useContext 等的内置钩子函数,我们也可以自定义钩子函数在组件之间来复用state。

useState(内置hook)

import {useState} from 'reaact';
function Example(){
const [count, setCount]=useState(0);
return(
	<div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
)}

在函数组件内部,不存在指向函数自身的this, 因此不可以声明或者调用this.state.
useState hooks提供给函数等同于class内部this.state的作用。state变量通过react进行维护。
useState 是一个HOOK, 在函数内部调用,可以给函数本身添加state,react会保存并渲染页面。useState返回一组值:当前State, 和更新state的方法,这个方法可以在函数内部或其他地方调用。与react this.setState方法类似,但是不同的是,它并非将旧值与新值进行合并得到新值.

useState只接受一个参数,即state的初始值,eg. useState(0), 0即为state的初始值。并且只在初次渲染时调用。与this.state不同的是,hook 的state不必一定为一个对象,但如果想定义为对象也是可以的。

定义多个State变量

function ManyStates(){
	const [age,setAge]=useState(42);
	const [fruit,setFruit]=useState('orange');
	const [todos,setTodos]=useState([{text:'learn hooks'}])

数组的解构赋值使我们可以定义多个变量。如果我们多次调用useState,react会同时进行编译。

Effect Hook(内置hook)

在react 组件内部,我们通常会进行一些数据获取,提交,或手动改变DOM状态的操作, 这种对其他环境有影响或依赖的又称为:side effect(副作用), Effect Hook: useEffect ,提供函数组件同样的功效,作用等同于class组件内部的componentDidMount, componentDidUpdate, componentWillUnmount,只是将这些统一成单一API。

import {useState, useEffect} from 'react';
function Eample(){
	const [count,setCount]=useState(0);
	// Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  return (
  	<div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
)
}

当调用useEffect,即告诉React在DOM变化后执行’effect’方法。Effects在组件内部声明,因此可以读取组件内的props, state。默认情况下,react会在每次render后都去执行effects里的方法,也包括第一次render.
Effects也可以通过返回一个函数选择性的指定如何’clean up’当函数unMount时。
eg.

import { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

上例中,React在组件卸载时会调用ChatAPI的unsubscribe方法,同样在随后的render中也会调用。(如果props.friend.id没有变化,你也可以告诉React忽略re-subscribing,如果你想。)

与useState类似,你也可以使用多个useEffect在函数组件中:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

上例中,多个useEffect里面传入的函数是彼此独立的,由此可以保证effect里面读取的state值将会是最新的,在网页每次重新编译后,我们执行一个新的effect,替代前面的state值。useEffect并不会阻塞浏览器更新页面,它们为异步执行。在某些特殊情况下需要同步执行,例如计算页面布局,这时可以使用useLayoutEffect Hook 替代useEffect. useLayoutEffect在DOM计算完成后同步执行,与componentDidMount, componentDidUpdate执行时间类似,在不确定用哪种effect时,使用useLayoutEffect风险较小。
Hooks可以将组件中相关的生命周期函数写在一起,而不是将它们分开在不同的生命周期函数中。

Effects without cleanup

在class组件中我们执行side effects在componentDidMount/componentDidUpdate中,如果我们想执行同一方法在组件每次render之后,必须将相同方法在didMount, didUpdate两个阶段都调用一次。
在Hooks中我们只需要在useEffects中将此方法写一次即可每次render后都进行调用。
默认情况下,useEffect在首次编译和每次更新数据都会执行,即每次编译后都执行。我们也可以进行优化:忽略某些编译。
在Class组件中,componentDidUpdate可以通过prevProps,prevState来阻止update里函数的执行eg.

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

在useEffect Hook API中内置了这种比较,你可以向useEffect(()=>{}, [compareState])传入第二个参数:state数组。eg.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

只有count值发生改变-上次render和此次render count值不同,react才会忽略此次render,不执行effect里的方法。
这个也同样作用于 含有clean up 的effects

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

如果使用这个优化,请确保数组内包含的值是从外部作用域获取,并且会随着时间变化,effect里面也会使用的。否则你effect里的代码将会从之前的编译中取值。
如果你想执行一个effect和clean up 仅一次,你可以将useEffect第二个参数传入一个空数组[], 类似componentDidMount, componentWillUnmount.

Effects with Cleanup
useEffects第一个参数会返回一个函数作为cleanup 执行的方法。会在unmount,和每次re-render时执行。

useContext(内置hook)

const context=useContext(Context);

class组件中context使用,会将组件包含在context.provider && context.consumer之间,consumer之间的组件会以render props的模式(传递一个函数作为children)来获得provider传递的参数,如下

import React from "react";
import ReactDOM from "react-dom";

// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }

function App() {
  // Use the Provider to make a value available to all
  // children and grandchildren
  return (
    <NumberContext.Provider value={42}>
      <div>
        <Display />
      </div>
    </NumberContext.Provider>
  );
}

function Display() {
  // Use the Consumer to grab the value from context
  // Notice this component didn't get any props!
  return (
    <NumberContext.Consumer>
      {value => <div>The answer is {value}.</div>}
    </NumberContext.Consumer>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));

在useContext hook 中,我们可以使用useContext(Context)-传入的参数为通过React.createContext获得的context对象,直接获取provider传递的值,改写consumer组件如下:

// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';

// ...

function Display() {
  const value = useContext(NumberContext);
  return <div>The answer is {value}.</div>;
} 

如果consumer组件中有多层嵌套获得多个provider值,那么使用hook将会更简单:

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}

//useContext改写如下:
function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);

  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}

useReducer Hook(内置)

Hooks规则

不要在循环,条件,或嵌套函数中使用,应该在顶层的React function中使用。由此确保在组件编译过程中,Hooks以相同的顺序进行调用,这保证了在众多useState, useEffect中React正确保存state状态。
Hooks只能在React 函数组件调用,常规函数不能调用。
eslint-plugin-react-hooks插件可以强化前两条规则: npm install eslint-plugin-react-hooks@next

// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title

// ...

自定义Hook

在传统React class组件中,如果组件之间公用大部分逻辑和页面,通常会通过props传值, 或使用高阶组件的方式来返回不同组件。
在Hook中,我们不需要写额外的组件,可以直接将共有部分抽取成自定义的hook函数,如下:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}`

useFriendStatus主要用于通过传入id返回online状态,所有组件共用同一个Hook,state也是相互独立的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值