Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
1、useState解析
useState特点
1、useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一
般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
2、useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为 undefined)。
3、useState是一个数组,我们可以通过数组的解构,来完成赋值会非常方便
使用规则:
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
2、只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
import React, { useState } from 'react'
import PropTypes from 'prop-types'
export default function CounterHook() {
/*1、HOOK userState
* 本身是一个函数来自react包
1、参数: 给创建出来的状态返回默认值
2、返回值:
元素1:当前state的值
元素2:设置新的值时使用新的函数
*/
const arr = useState(0);
const state = arr[0];
const setState = arr[1];
console.log(state);
return (
<div>
<h2>当前技术{state}</h2>
<button onClick={e => setState(state + 1)}>+1</button>
<button onClick={e => setState(state - 1)}>-1</button>
</div>
)
}
复杂状态操作
import React, { useState } from 'react'
function ComplexHookState() {
const [friends, setFriends] = useState(["kobe", "lilei"]);
const [students, setStudents] = useState([
{id: 112, name: "lilei", age: 25},
{id: 145, name: "xiaoming", age: 13},
{id: 234, name: "张明", age: 76},
])
function addFriend() {
friends.push("hmmm");
setFriends(friends);
}
function studentAgeWithIndex(index) {
const newStudents = [...students];
newStudents[index].age += 1;
setStudents(newStudents);
}
return (
<div>
<h2>好友列表:</h2>
<ul>
{
friends.map((item, index) => {
return <li key={item}>{item}</li>
})
}
</ul>
<button onClick={e => setFriends([...friends, "tom"])}>添加朋友</button>
<button onClick={addFriend}>添加朋友</button>
<h2>学生列表</h2>
<ul>
{
students.map((item, index) => {
return (
<li key={item.id}>
<span>名字:{item.name} 年龄:{item.age}</span>
<button onClick={e => studentAgeWithIndex(index)}>age + 1</button>
</li>
)
})
}
</ul>
</div>
)
}
export default ComplexHookState
2、Effect Hook
你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
2.1、useEffect
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API,
useEffect要求传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数
默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个 回调函数;
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2.2、清除Effect
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B
type EffectCallback = () => (void | (() => void | undefined));
每个 effect 都可以返回一个清除函数,可以将添加和移除订阅的逻辑放在一起
import React, { useEffect, useState } from 'react'
function EffectHookCancelDemo() {
const [show, setShow] = useState(true);
const [count, setCount] = useState(0);
useEffect(() => {
console.log("订阅一些事件")
return (() => {
// 取消订阅事件
console.log("取消订阅事件")
})
},[])
return (
<div>
{show && <h2>EffectHookCancelDemo</h2>}
<h2>{count}</h2>
<button onClick={e => setShow(!show)}>switch</button>
<button onClick={e => setCount(count + 1)}>+1</button>
</div>
)
}
export default EffectHookCancelDemo
2.3、多个Effect
import React, { useEffect, useState } from 'react'
function MultiEffectHookDemo() {
const [show, setShow] = useState(true);
const [count, setCount] = useState(0);
const [isLogin, setLogin] = useState(true);
useEffect(() => {
console.log("修改dom", count);
}, [count])
useEffect(() => {
console.log("订阅事件");
},[]);
useEffect(() => {
console.log("网络请求");
},[]);
return (
<div>
{show && <h2>MultiEffectHookDemo</h2>}
<h2>{count}</h2>
<button onClick={e => setShow(!show)}>switch</button>
<button onClick={e => setCount(count + 1)}>+1</button>
<h2>{isLogin ? "登录": "注销"}</h2>
<button onClick={e => setLogin(!isLogin)}>登录/注销</button>
</div>
)
}
export default MultiEffectHookDemo
3、useContext的使用
App.js文件
import React, { createContext, PureComponent, useContext, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'
export const UserContext = createContext()
export const ThemContext = createContext()
class App extends PureComponent {
static propTypes = {}
constructor(props) {
super(props)
}
render() {
return (
<UserContext.Provider value={{name: "why", age: 18}}>
<ThemContext.Provider value={{fontSize: "30px", color: "red"}}>
<ContextHookDemo />
</ThemContext.Provider>
</UserContext.Provider> */}
)
}
}
export default App
context.js
import React, { useContext } from 'react'
import { ThemContext, UserContext } from '../App'
function ContextHookDemo() {
const user = useContext(UserContext)
const them = useContext(ThemContext)
console.log(user, them);
return (
<div>
ContextHookDemo
</div>
)
}
export default ContextHookDemo
4、useReducer
useReducer仅仅是useState的一种替代方案,在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分,或者这次修改的state需要依赖之前的state时,也可以使用
它们的数据不会共享,只是使用了相同的counterReducer的函数,所以useReducer只是useState的一种替代品,并不能替代Redux
home.js
import React, {useReducer} from 'react'
import { reducer } from './reducer';
function Home() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<div>
<h2>Home当前计数:{state.count}</h2>
<button onClick={e => dispatch({type: "increment"})}>+1</button>
<button onClick={e => dispatch({type: "decrement"})}>-1</button>
</div>
)
}
export default Home
profile.js
import React, { useReducer } from 'react'
import { reducer } from './reducer';
function Profile() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<div>
<h2>Home当前计数:{state.count}</h2>
<button onClick={e => dispatch({type: "increment"})}>+1</button>
<button onClick={e => dispatch({type: "decrement"})}>-1</button>
</div>
)
}
export default Profile
reducer.js
export function reducer(state, action) {
switch(action.type) {
case "increment":
return {...state, count :state.count + 1}
break;
case "decrement":
return {...state, count :state.count - 1}
break;
}
}
5、useCallback
useCallback实际的目的是为了进行性能的优化,返回一个函数的 memoized(记忆的) 值,在依赖不变的情况下,多次定义的时候,返回的值是相同的,
React官网解释:把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染
import React, { memo, useCallback, useState } from 'react'
/*
useCallback
将组建中的函数,传递给子元素回调使用时,使用useCallback对函数进行处理
*/
const HYButton = memo((props) => {
console.log("HYButton 重新渲染" + props.title)
return (
<button onClick={props.increment}>HYButton +1</button>
)
})
function CallbackHookDemo02() {
const [count, setCount] = useState(0)
const [show, setShow] = useState(0)
const increment = () => {
console.log("执行increment函数")
setCount(count + 1)
}
const increment2 = useCallback(() => {
console.log("执行increment2函数")
setCount(count + 1)
}, [])
return (
<div>
<h2>CallbackHookDemo01:{count}</h2>
{/* <button onClick={e => increment()}>+1</button>
<button onClick={e => increment2()}>+1</button> */}
<HYButton title={"btn1"} increment={increment}/>
<HYButton title={"btn2"} increment={increment2}/>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
export default CallbackHookDemo02
6、useMemo
useMemo实际的目的也是为了进行性能的优化,把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
import React, { memo, useMemo, useState } from 'react'
const HYInfo = memo((props) => {
console.log("HYInfo重新渲染")
return <h2>名字: {props.name} 年龄:{props.age}</h2>
})
function MemoHookDemo02() {
console.log("MemoHookDemo02重新渲染")
const [show, setShow] = useState(false)
const info = useMemo(() => {
return {name: "why", age: 18}
}, []);
return (
<div>
<HYInfo info={info}/>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
export default MemoHookDemo02
7、useRef
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。
用法:
1、引入DOM(或者组件,但是需要是class组件)元素
2、保存一个数据,这个对象在整个生命周期中可以保存不变;
import React, { useEffect, useRef, useState } from 'react'
function RefHookDemo02() {
const [count, setCount] = useState(0)
const numRef = useRef(10); // useRef 返回生命周期中最初始的值
useEffect(() => {
numRef.current = count
})
return (
<div>
<h2>numRef中的值: {numRef.current}</h2>
<h2>count中的值: {count}</h2>
<button onClick={e => setCount(count + 10)}>+10</button>
</div>
)
}
export default RefHookDemo02
8、useImperativeHandle
通过forwardRef可以将ref转发到子组件,子组件拿到父组件中创建的ref,绑定到自己的某一个元素中
forwardRef的做法本身没有什么问题,但是将子组件的DOM直接暴露给了父组件带来的问题是某些情况的不可控
import React, { forwardRef, useRef } from 'react'
const HYInput = forwardRef((props,ref) => {
return <input ref={ref} type="text"></input>
})
function ForwardRefDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
export default ForwardRefDemo
通过useImperativeHandle可以值暴露固定的操作:
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
const HYInput = forwardRef((props,ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
console.log("useImperativeHandle 中的回调函数返回对象的focus")
}
}), [inputRef.current])
return <input ref={inputRef} type="text"></input>
})
function UseImperativeHookDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
export default UseImperativeHookDemo
9、useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已
1、useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新。
2、useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新。
如果希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect

import React, { useEffect, useLayoutEffect, useState } from 'react'
function EffectLayoutCounterDemo() {
const [count, setCount] = useState(10)
const [show, setShow] = useState(false)
useLayoutEffect(() => {
if(count == 0) {
setCount(Math.random());
}
}, [count])
return (
<div>
<h2>数字:{count}</h2>
<button onClick={e => setCount(0)}>修改数字</button>
</div>
)
}
export default EffectLayoutCounterDemo
网友评论