react16.8开始推出hooks,在兼容之前的类组件的同时,更加倾向于函数式编程。
为什么要使用hooks,个人理解:函数组件相比类组件更加轻量,类组件在继承时会把内部的所有方法全部继承下来,而hooks则采用函数式编程,只有在你需要的时候才需把这些钩子函数引入进来。
首先,react hook方法必须在函数组件内部调用,否则直接报错。
useState
用于在函数组件中声明状态和改变状态的方法:
// 这里用到了es6当中解构赋值的新特性
const [状态名, 改变状态的方法] = useState(状态的初始值);
For example:
import React, { useState } from 'react';
const HookComponent = () => {
const [conut, setCount] = useState(1)
return (
<div>
React 16.8 hooks
<button onClick={() => setCount(conut+1)}>conut+1</button>
</div>
)
}
export default HookComponent;
上面的操作当中,调用 setCount 会直接用新值覆盖原来 conut 状态中的值, 和类组件中setState中的合并有些区别。
同样的方式多次调用,就可以生成多个状态和多个改变状态的方法。
useEffect
在函数组件中我们想要执行一些副作用操作(诸如:调用API、开启定时器、修改组件状态等)时,可以选择 useEffect 这个钩子。
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。
这是 useEffect 的第一个用法,类似于类组件中的componentDidMount,不同的是这个函数可以在组件内部调用多次。
import React, { useEffect } from 'react';
const HookComponent = () => {
useEffect(() => {
console.log('didMount')
// 调用API
// 更改状态
// 开启定时器
// 绑定事件
// and so on...
}, []) // 第二个参数传空数组表示只在组件初始化时执行一次
return (
<div>
React 16.8 hooks
</div>
)
}
export default HookComponent;
useEffect的第二个用法:可以订阅状态或属性的变化而执行回调函数,同时也类似于类组件当中的componentDidUpdate
import React, { useEffect, useState } from 'react';
const HookComponent = () => {
const [conut, setCount] = useState(1)
// 每当 count 改变 就会执行回调函数
useEffect(() => {
// 第二个参数传多个值时,可在内部进行逻辑判断,只在需要的时候执行回调。
console.log(conut)
},[conut]) // 第二个参数为一个数组,内部可订阅多个状态和属性。
return (
<div>
React 16.8 hooks
<button onClick={() => setCount(conut+1)}>conut+1</button>
</div>
)
}
export default HookComponent;
useEffect 第三个用法:清除副作用方法,类似于类组件当中的componentWillUnmount,在组件即将被卸载时执行回调
useEffect(() => {
let time = setInterval(() => {
console.log('time')
}, 1000);
// 组件即将被卸载时执行回调
return () => clearInterval(time)
},[])
useRef
import React, { useEffect, useRef } from 'react';
const HookComponent = () => {
// 声明ref
const btn = useRef(null);
useEffect(() => {
// 打印ref
console.log(btn)
},[])
return (
<div>
React 16.8 hooks
{/* 绑定ref */}
<button ref = {btn}>count+1</button>
</div>
)
}
export default HookComponent;
useContext
const value = useContext(MyContext);
接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
调用了 useContext 的组件总会在 context 值变化时重新渲染。所以建议在context.provider组件中减少属性的直接更改,条件允许的情况下尽量将更改属性的方法传递给子组件,让子组件在需要的时候进行修改,减少不必要的Dom重新渲染。
第一步:定义context
import React, { useState } from 'react';
// 创建context,并初始化值
const MyContext = React.createContext({
defaultColor: "",
setDefaultColor: () => {}
})
// 管理value
export const useMyContext = () => {
const [defaultColor, setDefaultColor] = useState("#999")
return {
defaultColor,
setDefaultColor
}
}
// 导出 Provider 用于包裹需要传递属性的子组件
export const MyContextProvider = MyContext.Provider;
// 直接导出 context 用于子组件 useContext 的时候调用
export default MyContext;
第二步:包裹需要订阅公共属性的子组件(这里我设置的全局,但事实上可以在需要的组件内部进行嵌套,甚至多层嵌套。多层嵌套时内部子组件只会订阅到离他最近的context中的属性和方法)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Route from './App';
import { MyContextProvider, useMyContext } from "./contexts/global";
const App = () => {
// 初始化需要传递的属性和方法
const value = useMyContext();
return (
// 嵌套子组件的同时还需要把需要的属性传递进去
<MyContextProvider value = {value}>
<Route/>
</MyContextProvider>
)
}
ReactDOM.render(<App />,document.getElementById('root'));
第三步:子组件调用context
import React, { useContext } from 'react';
import MyContext from "../contexts/global";
const HookComponent = () => {
const value = useContext(MyContext);
console.log(value.defaultColor); // #999
return (
<div>
React 16.8 hooks
</div>
)
}
export default HookComponent;
useMemo
类似于Vue的computed计算属性,不过 useMemo 只会在订阅的属性或者状态发生改变的时候才会重新进行计算,避免每次重新渲染页面时都进行高耗能的计算。
但同理,如果第二个参数传入空数组,则依然会在每次组件初始化时执行回调函数。但这里要注意,即使如此依然不要在 useMemo 内部进行副作用操作。 所有的副作用操作都要放在 useEffect 当中去做。
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回a + b之和, 当a或b改变时重新计算
const sum = useMemo(() => {
return a + b
}, [a, b])
useCallback
把回调函数及依赖项数组作为参数传入 useCallback,它将返回一个回调函数,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。都是用于react性能优化和避免不必要的重复渲染。
const memoizedCallback = useCallback(() => {
doSomething(a, b);
},[a, b]);