概述
要求react版本必须是16.8以上,React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
使用
使用规则
1、只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState
和 useEffect
调用之间保持 hook 状态的正确。
2、只在 React 函数中调用 Hook
**不要在普通的 JavaScript 函数中调用 Hook。**你可以:
-
✅ 在 React 的函数组件中调用 Hook
-
✅ 在自定义 Hook 中调用其他 Hook
遵循此规则,确保组件的状态逻辑在代码中清晰可见。
例子
import { React, useState } from "react";
export default function Test2() {
const [count, setCount] = useState(0);
return (
<div>
<h1>count的值是{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
点我count+1
</button>
</div>
);
}
最简单的count+1的例子
四大钩子——useState
useState()
用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
useState()
这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是count
),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set
前缀加上状态的变量名(上例是setCount
)。
上面例子已经做了🌰了
声明多个state
function ExampleWithManyStates() {
// 声明多个 state 变量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
与 class 组件中的 setState
方法不同,useState
不会自动合并更新对象。你可以用函数式的 setState
结合展开运算符来达到合并更新对象的效果。
const [state, setState] = useState({});
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
惰性初始 state
initialState
参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
四大钩子——useEffect
useEffect()
用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在
componentDidMount
、componentDidUpdate
、componentWillUnmount
里面的代码,现在可以放在useEffect()
。
useEffect(() => {
// Async Action
}, [dependencies])
上面用法中,
useEffect()
接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()
就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()
。
import { React, useEffect, useState } from "react";
export default function Test2() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count----", count);
}, [count]);
return (
<div>
<h1>count的值是{count}</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
点我count+1
</button>
</div>
);
}
可以看到一打开页面就看到输出了count---- 0
,然后没有加1的时候又输出了count的值,
**useEffect**
会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后_和_每次更新之后都会执行。
useEffect(() => {
//可以在这里发axios请求、订阅事件
console.log("count----", count);
return function cleanUp(){
//在这里关闭axios请求、关闭订阅事件
console.log('Hello');
}
}, [count]);
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect? React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
虽然 useEffect
会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React 总会先清除上一轮渲染的 effect。
四大钩子——useContext
useContext——共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()
现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。
*
第一步就是使用 React Context API,在组件外部建立一个 Context。
const AppContext = React.createContext({});
组件封装代码如下。
*
上面代码中,
AppContext.Provider
提供了一个 Context 对象,这个对象可以被子组件共享。
Navbar 组件的代码如下。
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
上面代码中,
useContext()
钩子函数用来引入 Context 对象,从中获取username
属性。
四大钩子——useReducer
useReducer():action 钩子
Reducer 函数的形式是(state, action) => newState
。useReducers()
钩子用来引入 Reducer 功能。
const [state, dispatch] = useReducer(reducer, initialState);
它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch
函数。
import { React, useReducer } from "react";
const myReducer = (state, action) => {
switch (action.type) {
case "countUp":
return {
...state,
count: state.count + 1,
};
case "countDown":
return {
...state,
count: state.count - 1,
};
default:
return state;
}
};
export default function Test3() {
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<div>
<button onClick={() => dispatch({ type: "countUp" })}>+1</button>
<button onClick={() => dispatch({ type: "countDown" })}>-1</button>
<p>Count: {state.count}</p>
</div>
);
}
补充
useRef
const refContainer = useRef(initialValue);
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内持续存在。
ref对象的值发生改变之后,不会触发组件重新渲染。
为什么需要useRef
-
函数组件访问DOM元素;
-
函数组件访问之前渲染变量。
函数组件每次渲染都会被执行,函数内部的局部变量一般会重新创建,利用useRef
可以访问上次渲染的变量,类似类组件的实例变量
效果。
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>
</>
);
}
useMemo
useMemo
和 memo
作用相同,都是用来做性能优化的,不会影响业务逻辑。
memo
函数,针对的是一个组件
的渲染,是否重复执行。(<Foo/>
)
useMemo
,定义了一段函数
逻辑,是否重复执行。(() => {}
)
useMemo
的第一个参数是函数
,第二个参数一般都为数组
。
如果不传第二个参数,与useEffect
类似,意味着每一次都会执行第一个函数参数,那么使用useMemo
的意义就没有了。
如果第二个参数传的是空数组[]
,与useEffect
类似,只执行一次。
useMemo
与useEffect
有不一样的一点就是调用时机 —— useEffect
执行的是副作用,所以一定是在渲染之后运行的;而useMemo
是需要有返回值的,返回值会参与渲染,所以useMemo
是是在渲染期间完成的。
import React, { useState, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2
}, [count === 3])
return (
<div>
<button onClick={() => setCount(count + 1)}>点我加一</button>
<h1>count: {count}</h1>
<h1>double: {double}</h1>
</div>
) }
export default App;
这里使用了count === 3
这个表达式的结果(布尔值,true/false),作为是否重新渲染Double组件的判断依据。
所以随着count
从0开始的每一次的加一,Double
组件最终也只会重新渲染两次:
-
当count值为3的时候,
count === 3
这个表达式的结果为true
,发生改变了。 -
当count值为4的时候,
count === 3
这个表达式的结果为false
,发生改变了。
useMemo中依赖的值也可以是useMemo
import React, { useState, useMemo } from "react";
function App() {
const [count, setCount] = useState(0);
const double = useMemo(() => {
return count * 2;
}, [count === 3]);
const half = useMemo(() => {
return count / 2;
}, [double]);
return (
<div>
<button onClick={() => setCount(count + 1)}>点我加一</button>
<h1>
count: {count} double: {double} half: {half}
</h1>
</div>
);
}
export default App;
useCallback
为什么要有useCallback这个钩子呢
1.useCallback会返回一个函数的memoized(记忆的)值
2.在依赖不变的情况下,多次定义的时候,返回的值是相同的
当我们修改父组件里面值的时候,子组件肯定也会重新渲染,但是我们不想它重新渲染,怎么做呢?
import React, { useState, useCallback,memo } from 'react'
const HYButton = memo((props)=>{
console.log(props.title)
return <button onClick={props.increament}>HYButton+1</button>
})
export default function UseCallbackDemoOne() {
const [count,setCount] = useState(0)
const [show,setShow] = useState(true)
const increament1 = () => {
console.log('执行increament1')
setCount(count + 1)
}
const increament2 = useCallback(()=>{
console.log('执行increament2')
setCount(count + 1)
},[count])
return (
<div>
<h2>{count}</h2>
<HYButton title="btn1" increament={increament1}/>
<HYButton title="btn2" increament={increament2}/>
<button onClick={e=>setShow(!show)}>切换</button>
</div>
)
}
当点击切换时,可以看到btn1重新渲染了,而btn2并没有,因为increament2依赖的count没有改变,所以每次返回都是相同的值,所以btn2就不会重新渲染