概述
react的hooks功能发布于v16.8版本,其目的在于解决状态共享的问题,也就是有状态组件的复用问题,在此之前的两种解决方案分别是渲染属性(Render Props)和高阶组价。
hooks使用的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中。)
useState
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
等价的class写法(方括号是es6中的数组解构的语法)
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
当存在多个useState时,react是按照执行顺序识别对应state的,比如这种情况:
const [fruit, setFruit] = useState('apple');
const [animal, setAnimal] = useState('cat');
const [color, setColor] = useState('green');
所以官方文档也有说明,hooks不能存在条件判断语句中。
useEffect
该hook官方解释作用是可以让你在函数组件中执行副作用操作,而且能够实现一定的性能优化。
关于副作用网上看到一个很简单易懂的解释:
- 纯函数(Pure function):给一个 function 相同的参数,永远会返回相同的值,并且没有副作用;这个概念拿到 React 中,就是给一个 Pure component 相同的 props, 永远渲染出相同的视图,并且没有其他的副作用;纯组件的好处是,容易监测数据变化、容易测试、提高渲染性能等;
- 副作用(Side Effect)是指一个 function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log(),所以 ajax 操作,修改 dom 都是算作副作用的;
import React, { useState, useEffect } from 'react';
function Example() {
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 ()=>{
console.log('xiezaile...')
}
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
我理解的该hook的作用就是相当于糅合了声明周期中的挂载、卸载、更新的钩子函数。
首先,第一个参数是个回调,默认(没有第二个参数时)在页面首次载入和页面有更新时调用,return的返回值也是个函数,在页面卸载时调用。
第二个参数我理解的是触发页面更新的条件变量,当它是空数组时,点击按钮count变化,但是第一个参数的回调函数不会执行,即使是props或state变化,第一个参数的回调函数也不会执行。但如果第二个参数是[count],表示count变化时,第一个回调函数也会调用。
由于第一个参数的回调函数内部是异步的,因此用来修改DOM不能达到及时改变的效果
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
return ()=>{
console.log('xiezaile...')
}
},[count]);
注:有些副作用需要清除时就带上返回函数,不需要处理时就不需要返回。
useContext
该hook主要用于上下文父子组件(非直系父子)传递数据。
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值(只要是对<Context.Consumer>写法的替代)。
import React, { useState , createContext, useContext } from 'react';
const countContext = createContext(); //创建上下文
function Counter(){ //接收数据的组件
const count = useContext(CountContext) //就相当于<countContext.Consumer></countContext.Consumer>得到count
return (<h2>{count}</h2>)
}
function Example(){
const [count,setCount] = useState(0)
return (
<div>
<p>this is {count}</p>
<button onClick={()=>{setCount(count+1)}}>增加</button>
<countContext.Provider value={count}> //当数据更新的时候触发渲染
<Counter></Counter>
</countContext.Provider>
</div>
)
}
export default Example;
useReducer
当state内容较为复杂时,可以作为它的替代方案,跟redux中的reducer处理部分相似。
官网用例:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [action,dispatch] = useReducer(reducer,{count: 0});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
useReducer结合useContext实现redux功能
import React, {useReducer,useContext,createContext} from 'react'
const CountContext = createContext()
function reducer(state,action){
switch(action.type){
case 'Asc':
return {count: state.count+1}
case 'Dsc':
return {count: state.count-1}
default:
new Error()
}
}
function GrandChild(){
console.log(useContext(CountContext),'cccccccc')
const {state,dispatch} = useContext(CountContext)
return (
<div>
{/* <div>{num}</div> */}
<div>{state.count}</div>
<button onClick={()=>dispatch({type: 'Asc'})}>adddddddd</button>
<button onClick={()=>dispatch({type: 'Dsc'})}>descccccc</button>
</div>
)
}
function Child(props){
return (
<div>
{props.children}
</div>
)
}
export default function App(){
const [state,dispatch] = useReducer(reducer,0,x=>({count:x}))
return (
<CountContext.Provider value={{state,dispatch}}>
<Child>
<GrandChild></GrandChild>
</Child>
</CountContext.Provider>
)
}
useMemo
官网用例
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值,如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。这种优化有助于避免在每次渲染时都进行高开销的计算。
传入 useMemo 的函数会在渲染期间执行,不要存在副作用,应把副作用的处理放在useEffect中
再来一个用例
//父组件
function Parent() {
const [name, setName] = useState('ali')
const [age,setAge] = useState('22')
return (
<>
<button onClick={() => setName(new Date().getTime())}>name</button>
<button onClick={() => setAge(new Date().getTime())}>age</button>
<Button name={name}>{age}</Button>
</>
)
}
//子组件
function Button({ name }) {
function changeName(name) {
console.log('11')
return name + '改变name的方法'
}
//const otherName = changeName(name) //在点击父组件按钮改变name或age时都会调用该函数
const otherName = useMemo(()=>changeName(name),[name]) //只在name改变时才调用该函数
return (
<>
<div>{otherName}</div>
</>
)
}
useCallback
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
这里有篇文章关于useMemo和useCallback的异同讲解的通俗易懂。
useRef
返回一个可变的 ref 对象,其 .current属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
官网用例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
注:当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
useImperativeHandle
不能在函数组件上使用ref属性,因为他们没有实例,但是使用useImperativeHandle,实现函数式组件使用ref,根本上是因为强制性的公开给父组件的实例值。 与 forwardRef 一起使用,官网实例:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
自定义Hook
有几点需要注意的:
- 命名必须以use开头,不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
- 两个组件中使用相同的hook不会共享state,每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全独立的
这里有一些hooks相关的学习资源:
本文为个人学习总结,如有错误欢迎指正和补充。