什么是hook?
在 react 官方文档中是这么介绍 hook 的
使用 hook的好处
- 提供涉及state和生命周期钩子逻辑的抽象和复用,解决了过去因为和组件耦合导致的state和钩子的复用问题
- 将props的链式数据传导转化为横切式的传导方式,减少了props的传导层级,简化了结构
- 使函数组件也能操作state,完善了函数式组件的功能,避免了因为逻辑的复杂化时要把函数组件改成class组件的麻烦
- 和render-props/高阶组件实现方案的对比:比起别扭的render-props来,Hook的代码结构清晰明了。而相比起高阶组件,hook有内置的API,使用起来更简单方便
Hook的使用要求
- Hook 是 React 16.8 的新增特性
- Hook只能在函数式组件或者自定义hook中使用,不能在class组件中使用
- Hook可以作为子组件和其他class组件一起嵌套在组件层级中
- Hook只能在顶层调用,不能在循环中使用,因为这可能会导致预料之外的bug
常见的Hook
- useState Hook
- Effect Hook
- useContext
- useReducer
- useCallback
- useMemo
- useRef
useState Hook
让函数组件具有 修改state的 能力,因为在函数式组件中,没有 this 。React 会在重复渲染时保留这个 state。
传参:useState(xxx)
方法里面唯一的参数就是初始 state, 将传入的值将作为属性的初始值,state 变量可以很好地存储对象和数组
返回值为:当前 state 以及更新 state 的函数。
import React, { useState } from 'react';
function Example() {
// 声明一个叫 count 的 state 变量 useState(0) 传参 0 为设置的初始值
const [count, setCount] = useState(0); //[] 数组解构 取到数组中对应位置的值 赋给相应变量
// 声明多个 state 变量,给不同的 state 变量取不同的名称 state 变量可以很好地存储对象和数组
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
return (
<div>
<!-- 在函数中,我们可以直接用 count -->
<p>You clicked {count} times</p>
<!-- 更新state setCount 和 count 变量,所以我们不需要 this -->
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
我们声明了一个叫 count
的 state 变量,然后把它设为 0
。React 会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数。我们可以通过调用 setCount
来更新当前的 count
。
在 state 中存储两个不同的变量,只需设置两个不同的 useState()
, 通过 useState 获取的数据之间相互隔离
Effect Hook
React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。当你调用 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>
);
}
由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。在每次渲染后调用副作用函数 —— 包括第一次渲染的时候,在react完成对 DOM 的更改后运行useEffect
。
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
useContext
实现跨组件间的数据传输
- 通过
createContext()
创建 MyContext 对象 并返回设置的初始值 - 父组件使用 <MyContext.Provider value={{xx:xx}}> 向后代组件传递信息,当 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,更新value值。
- 在子组件中使用 useContext(MyContext) 获取上下文
- context-manager.js – 定义 context
import {createContext} from 'react';
export const MyContext = createContext(null);
- useContext 的使用
import React, { useState } from 'react';
import Child from './Child';
import { MyContext } from './context-manager';
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
})
});
}
export default (props = {}) => {
const [step, setStep] = useState(0);
const [count, setCount] = useState(0);
const [number, setNumber] = useState(0);
return (
<MyContext.Provider value={{ setStep, setCount, setNumber, fetchData }}>
<Child step={step} number={number} count={count} />
</MyContext.Provider>
);
}
userReducer
reducer 与 redux 的区别:
- reducer 用于单个组件的状态管理,适用于复杂的 state 变化
- redux 是全局状态管理,支持多组件间的共享数据
- 在组件间的通信还是使用props
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的。
count 是返回状态中的值,而 dispatch 可以发布事件来更新 state 。
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。
import React,{useReducer} from 'react'
export default function ReducerDemo() {
const [count, dispath] = useReducer((state,action)=> {
switch(action){
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}, 0);
return (
<div>
<h1 className="title">{count}</h1>
<button className="btn is-primary"
onClick={()=> dispath('add')}
>Increment</button>
<button className="btn is-warnning"
onClick={()=> dispath('sub')}
>Decrement</button>
</div>
)
}
自定义Hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
定义Hook – useFriendStatus
在自定义组件中可以调用其他 hook 如:useState, useEffect ,可以在在多个 Hook 之间传递信息
import { useState, useEffect } from 'react';
// friendID 作为函数的参数
function useFriendStatus(friendID) {
// 设置属性
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
// 在组件初次渲染时(componentWillMount)、更新时(componentWillUpdate)调用
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
// return 的清除机制 在组件销毁时调用 ===>>> componentWillUnmount
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
// 返回好友在线的状态
return isOnline;
}
在两个组件中使用相同的 Hook 的state 相互隔离
自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。 两个组件获取到的 useState 相互隔离
自定义Hook的使用
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function ChatRecipientPicker() {
// 通过 useState 初始化状态, 并返回更新状态的函数
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))} > <!-- 事件传参 -->
<!-- 循环渲染 list -->
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
当我们选择不同的好友并更新 recipientID
状态变量时,useFriendStatus
Hook 将会取消订阅之前选中的好友,并订阅新选中的好友状态。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作。useMemo
在每次渲染时都会计算新的值。
你可以把 useMemo
作为性能优化的手段,但不要把它当成语义上的保证。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。
useRef
通过 ref 访问 DOM,useRef
会在每次渲染时返回同一个 ref 对象。
ref 的特性
- 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )
- 返回的 ref 对象在组件的整个生命周期内保持不变
- 当更新 current 值时不会引发组件重新渲染
- 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
- useRef 类似于类组件的 this
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
// 通过 ref 获取 dom 元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
渲染 <FancyInput ref={inputRef} />
的父组件可以调用 inputRef.current.focus()。