Hooks是React v16.7.0-alpha的新特新,可以与react state相结合, 使函数组件功能类似于class一样。但是在Class类内部,不可以使用hooks.
React 提供了几个像useState,useEffect, useContext 等的内置钩子函数,我们也可以自定义钩子函数在组件之间来复用state。
import {useState} from 'reaact';
function Example(){
const [count, setCount]=useState(0);
return(
You clicked {count} times
)}
在函数组件内部,不存在指向函数自身的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不必一定为一个对象,但如果想定义为对象也是可以的。
function ManyStates(){
const [age,setAge]=useState(42);
const [fruit,setFruit]=useState('orange');
const [todos,setTodos]=useState([{text:'learn hooks'}])
数组的解构赋值使我们可以定义多个变量。如果我们多次调用useState,react会同时进行编译。
在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 (
You clicked {count} times
)
}
当调用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时执行。
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 (
);
}
function Display() {
// Use the Consumer to grab the value from context
// Notice this component didn't get any props!
return (
{value => The answer is {value}.}
);
}
ReactDOM.render( , 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 The answer is {value}.;
}
如果consumer组件中有多层嵌套获得多个provider值,那么使用hook将会更简单:
function HeaderBar() {
return (
{user =>
{notifications =>
Welcome back, {user.name}!
You have {notifications.length} notifications.
}
}
);
}
//useContext改写如下:
function HeaderBar() {
const user = useContext(CurrentUser);
const notifications = useContext(Notifications);
return (
Welcome back, {user.name}!
You have {notifications.length} notifications.
);
}
// ------------
// 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
// ...
在传统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也是相互独立的。