Hooks 的使用规则
-
只能在函数的顶级作用域使用。
- 顶级作用域:就是 Hooks 不能在循环、条件判断或者嵌套函数中执行,而必须是在顶层。 同时,Hooks 在组件的多次渲染之间,必须按照顺序被执行。
-
只能在函数组件或者其他 Hooks 中使用。
- Hooks 作为专门为函数组件设计的机制,使用的情况有两种:在函数组件内、在自定义的 Hooks 里面。
Hooks 的好处
- 可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新。
- 最大的好处:使你在无需修改组件结构的情况下复用逻辑状态,简化了逻辑服用。
- 另一个好处:有助于关注分离。
- Hooks 解决了 Class 组件存在的一些代码冗余、难以逻辑复用的问题。
怎样让函数组件不会太过冗长?
- 尽量将相关的逻辑做成独立的 Hooks,然后在函数组件中使用这些 Hooks,通过参数传递和返回值让 Hooks 之间完成交互。
Hooks 的本质
- 提供了让 React 组件能够绑定到某个可变数据源的能力。
useState
作用
- 让函数组件具有维持状态的能力。
遵循原则
-
state 中永远不要保存可以通过计算得到的值。
- 从 props 传递过来的值。
- 从 url L中读取到的值。
- 从 cookie、localStorage 中读取的值。
-
state 虽然便于维护状态,但也有自己的弊端。一旦组件有自己的状态,意味着组件如果重新创建,就需要有恢复状态的过程,这通常会让组件变的更复杂。
-
useState 收集依赖,等到下次在执行的时候,根据依赖重新获取最新的状态,或者更新后的状态。
-
useState 是使用 闭包 + 链表 的形式实现的。
使用方法
const [count, setCount] = useState(0);- useState(initialState) 的参数是一个初始值,可以是任意类型的值;
- 它有两个返回值,一个 state ,以及一个用来更新 state 的函数;
- setState 函数用于更新 state,它接收一个新的 state 值,并将组件的一次重新渲染加入队列。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
- 如果要创建多个 state,就需要调用多个 useState。
两种使用方法的区别:
- 通过一个新的 state 值更新。当点击多次时,只有一次点击事件生效,因为 count 值是没有变化的。
- 通过函数更新返回新的 state。使用函数式更新 state 时,就不会出现点击多次,只有一次点击事件生效的情况,因为它可以获取到之前的 state 值,也就是说 prevCount 每次都是最新的值。
第一种:setCount(count + 1);
第二种:setCount2((prevCount) => { return prevCount + 1;});
示例
比如:声明了一个名为 count 的 state,当要更新数据时,使用 setCount 进行更新数据,当调用 setCount 时,count 这个state就会更新,并触发组件的刷新。
import React, { useState } from 'react';
function Example() {
// 创建一个保存 count 的 state,并给初始值 0
const [count, setCount] = useState(0);
// 定义一个年龄的 state,初始值是 42
const [age, setAge] = useState(42);
// 定义一个水果的 state,初始值是 banana
const [fruit, setFruit] = useState('banana');
// 定一个一个数组 state,初始值是包含一个 todo 的数组
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}> + </button>
</div>
);
}
useEffect
作用
- 可以在函数组件中执行副作用操作。
- 监听数据变化,变化后会执行方法,或者会执行第一个参数。
- 通过这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数,并且在只能怪 DOM 更新之后调用它。
副作用
- 是指一段和当前执行结果无关的代码。改变 DOM、添加订阅、设置定时器、记录日志,都是副作用。
特殊用法
- 没有依赖项,则每次 render 后都会重新执行。
- 每次 render 执行 先卸载、后创建 的顺序。
为什么在组件内部调用 useEffect?
- 将 useEffect 放在组件内部让我们可以在 effect 中直接访问 state 变量。
useEffect 会在每次渲染后都执行吗?
- 默认情况下,它在第一次渲染之后和每次更新之后都会执行。
为什么要在 effect 中返回一个函数?
- 这是 effect 可选的清除机制,每个 effect 都可以返回一个清除函数。
React 何时清除 effect?
- react 会在页面卸载的时候执行清除操作。
- 通过这样一个简单的机制,我们能够更好的管理副作用,从而确保组件和副作用的一致性。
- useEffect 当依赖发生变化时,可以异步执行里面的回调。
使用方法
useEffect(()=>{}, []);- useEffect 接收两个参数,useEffect(callback, dependencies),第一个参数为要执行的函数 callback, 第二个是可选的依赖项数组 dependencies,依赖项可接受多个参数。
- 其中,依赖项是可选择的,如果不指定,那么 callback 就会在每次函数组件执行完之后都执行;如果指定了,那么只有依赖项的值发生变化的时候,它才会执行。
- 对应到 Class 组件,那么 useEffect 就涵盖了 componentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。
- useEffect 是每次组件 render 完后判断依赖并执行。
示例
- 每次 render 完一定执行:不提供第二个依赖项参数。
useEffect(() => {
console.log('每次 render 完一定执行');
})
- 仅第一次 render 后执行:提供一个空数组作为依赖项,只有首次执行时触发。 对应 class 组件的 componentDidMount。
useEffect(() => {
// 组件首次渲染时执行,等价于 class 组件中的 componentDidMount
console.log("执行一次");
}, []);
- 第一次以及依赖项发生变化后执行:提供依赖项数组。
useEffect(() => {
console.log("依赖项发生变化时执行");
}, [id, count]);
- 组件 unmount 后执行:返回一个回调函数,用于在组件销毁的时候做一些清理的操作。 对应 Class 组件中的 componentWillUnmount。返回的回调函数是卸载。
useEffect(() => {
return () => {
console.log("======模仿componentWillUnMount");
};
}, []);
- 自定义 hooks,模仿 componentDidUpdate
/**
* 调用 自定义 hooks 组件:useDidUpdate
*/
useDidUpdate(() => {
console.log('模仿componentDidUpdate====')
})
/**
* 自定义的 hooks 组件:useDidUpdate
*/
import React, {useEffect, useRef} from 'react';
/**
* 模仿 componentDidUpdate
* @param cb 传过来的回调函数
*/
export default (cb) => {
const isMount = useRef(true)
useEffect(() =>
/**
* 初始化的时候进入该判断,然后我们把条件置为 false
* 并且阻塞后续的程序运行
*/
if (isMount.current) {
isMount.current = false
return
}
/**
* 在这里调用传过来的回调函数
*/
cb()
})
};
useCallback
作用
- 缓存回调函数。
- 用来优化子组件,防止子组件的重复渲染。
使用方法
useCallback(fn, deps)。- useCallback 有两个参数,第一个参数是定义的回调函数,第二个参数是依赖的变量数组。
- useCallback 大部分使用场景是搭配 react.memo 使用。
- 用 useCallback 包裹的函数,根据依赖是否发生变化,才会决定是否返回一个新的函数,如果没有变化,就会返回上一次缓存的函数。 这样就保证了组件不会创建重复的回调函数,而接收这个回调函数作为属性的组件,也不会频繁的需要重新渲染。
- 只有当依赖项发生变化时,才会重新创建回调函数。
第二个参数
| 第二个参数 | 何时生成新函数 |
|---|---|
| 空 | 组件首次执行及更新都会生成新函数 |
| [] 空数组 | 组件首次执行生成,之后不变 |
| [count,id] 变量数组 | 组件首次执行、依赖变化时生成新函数 |
示例
import React, {memo, useCallback, useState} from 'react';
/**
* React.memo 这个方法,会对 props 做一个浅层比较,如果 props 没有发生改变,则不会重新渲染此组件。
* 没有用 useCallback 包裹的函数,每次都会重新声明一个方法,新方法虽然和旧方法一样,但是依旧是两个不同的对象,
* React.memo 对比后发现 props 改变,就会重新渲染。
* @type {React.NamedExoticComponent<{readonly onClick?: *, readonly children?: *}>}
*/
const Button = memo(({onClick, children}) => {
return (
<>
<button onClick={onClick}>{children}</button>
<span>{Math.random()}</span>
</>
);
});
const HooksUseCallback = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
/**
* Button1 事件:只会更新 Button1 和 Button3 后面的内容
* 只要父组件更新后都会更新
*/
const handleClickButton1 = () => {
setCount1(count1 + 1)
};
/**
* Button2 事件:会将三个按钮后的内容都更新
* 经过 useCallback 优化后的 Button2 是点击自身时才会更新
* @type {(function(): void)|*}
*/
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
<div>
<Button onClick={handleClickButton1}>Button1</Button>
<Button onClick={handleClickButton2}>Button2</Button>
{/* Button3 事件:也是只更新 Button1 和 Button3 后面的内容 */}
{/* 只要父组件更新后都会更新 */}
<Button onClick={() => {setCount3(count3 + 1);}}>Button3</Button>
</div>
)
};
export default HooksUseCallback;
useMemo
作用
- 缓存函数的计算结果。
- 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memoize 来将一些复杂的计算逻辑进行缓存。
使用方法
useMemo(fn, deps)。- 接收两个参数。第一个参数 fn 是产生所需数据的一个计算函数, 第二个参数是依赖项。
- 返回一个 memoized 值, 只有当它的某个依赖项改变时才重新计算 memoized 值,初始化的时候也会调用一次,这种优化有助于避免在每次渲染时都进行高开销的计算。
- 创建一个函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生变化时,才会重新调用此函数,返回一个新的值。
第二个参数
| 第二个参数 | 何时生成新函数 |
|---|---|
| 空 | 组件首次执行及更新都会生成新函数 |
| [] 空数组 | 组件首次执行生成,之后不变 |
| [count,id] 变量数组 | 组件首次执行、依赖变化时生成新函数 |
useMemo 的好处
- 避免重复计算。
- 避免子组件的重复渲染。
使用 useMemo 实现 useCallback
- useCallback 和 useMemo 从本质上来说,它们只是做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当一来变了,这个结果才需要被重新得到。
const myEventHandler = useMemo(() => {
// 返回一个函数作为缓存结果
return () => {
// 在这里进行事件处理
}
}, [dep1, dep2]);
示例
// 使用 userMemo 缓存计算的结果
const usersToShow = useMemo(() => {
if (!users) return null;
return users.data.filter((user) => {
return user.first_name.includes(searchKey));
}
}, [users, searchKey]);
使用 useMemo 示例
import React, {useMemo, useState} from 'react';
const LearnUsememo = () => {
const [num, setNum] = useState(1);
const [num1, setNum1] = useState(1);
/**
* 第一个参数:一个函数
* 第二个参数:依赖项
*
* 如果依赖项传空的话,只会在初始化的时候调用一次
* 调用的是第一个函数,然后拿他的返回值,也就是说
* 下面的 count 就是返回值
*
* 注意:如果 useMemo 里面使用了,相关的 useState 或者 props 里面的值
* 进行计算,那么就需要注意依赖项要传入这个值
* 不然,计算结果可能都是老值
* @type {number}
*/
const count = useMemo(() => {
console.log('我是调用了')
return num * 10
}, [num]);
return (
<div>
<div>useMemo</div>
{count}
<button onClick={() => setNum(num + 1)}>+1</button>
{num} - {num1}
<button onClick={() => setNum1(num1 + 1)}>+1</button>
</div>
);
};
export default LearnUsememo;
useRef
作用
- 在多次渲染之间共享数据。
- 用来获取 DOM 元素对象。
- 保存数据。
useRef返回一个可变的 ref 对象,其.current属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
好处
- 保存的数据不会因为组件的更新而丢失。
使用方法
const myRefContainer = useRef(initialValue);
import React, {useEffect, useRef, useState} from 'react';
const HooksUseRef = () => {
const [count, setCount] = useState(0)
const timerId = useRef();
useEffect(() => {
timerId.current = setInterval(() => {
setCount(e => e + 1)
}, 1000)
}, [])
const stop = () => {
clearInterval(timerId.current)
}
return (
<div>
{count}
<button onClick={stop}>停止</button>
</div>
);
};
export default HooksUseRef;
示例
// 子组件
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { Modal } from 'antd';
import MatrixTextArea from '@/components/Matrix/MatrixTextArea';
// React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数。
// 第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
const CancelComponent = (props, ref) => {
const { title, handleSubmit, form, id = 'id', placeholder = '请输入',required='false' } = props;
const [visible, setVisible] = useState(false);
const [confirmLoading,setConfirmLoading] = useState(false)
const statusRef = useRef(true);
// 可以让你在使用 ref 时自定义暴露给父组件的实例值,搭配 forwardRef 使用
useImperativeHandle(ref, () => ({
showHidden: (vis) => {
setVisible(vis);
},
showLoading:(vis) => {
setConfirmLoading(vis);
},
}));
const onOk = () => {
form.validateFields([id], (errors, values) => {
if (errors) return;
if (handleSubmit) handleSubmit(values);
});
};
return (
<Modal
visible={visible}
title={title}
confirmLoading={confirmLoading}
onOk={onOk}
onCancel={() => setVisible(false)}
maskClosable={false}
destroyOnClose
ref={statusRef}
>
<MatrixTextArea id={id} form={form} md={0} placeholder={placeholder} required={required} maxLength={300} style={{ minHeight: 80 }} />
</Modal>
);
};
export default forwardRef(CancelComponent);
// 父组件函数式组件调用:
const childRef = useRef(null);
const handleSubmit = async (e) => {
childRef.current.showLoading(true);
dispatch({ type: 'carInStore/fetchCancelStore',payload: { ...e, id: stockData.id },}).then(() => {
childRef.current.showHidden(false); // 调用子组件的 弹窗按钮显示隐藏
childRef.current.showLoading(false);// 调用子组件的 弹窗loading显示隐藏
handleSearch(param);
});
};
<CancelComponent title="取消入库" placeholder="请输入取消入库原因" id="cancelReason" handleSubmit={handleSubmit} required ref={childRef} form={form} />
// 父组件类式组件调用:
handleSubmit = (values) => {
const { rowData, params } = this.state;
const data = { ...values, ids: rowData.id, type: 3, flag: 2,};
const { dispatch } = this.props;
const refresh = this.handleSearch;
this.child.showLoading(true);
dispatch({ type: 'receipt/confirm', payload: data,}).then(() => {
refresh(params);
this.child.showHidden(false); // 调用子组件的 弹窗按钮显示隐藏
this.child.showLoading(false);// 调用子组件的 弹窗loading显示隐藏
});
};
<CancelComponent title="驳回原因" placeholder="请输入驳回原因" id="reason" handleSubmit={this.handleSubmit} required ref={ref => this.child = ref} form={form} />
useImperativeHandle
作用
- 可以让你在使用 ref 时自定义暴露给父组件的实例值。
使用方法
-
useImperativeHandle 应当与 forwardRef 一起使用。
-
useImperativeHandle(ref,()=>{})。接收两个参数,第一个参数:父组件传递的 ref 属性,第二个参数:返回一个对象,以供给父组件中通过 ref.current 调用该对象的方法。 -
ref 和 forwardRef 的结合使用:
-
forwardRef:通过 forwardRef 可以将 ref 转发给子组件。
-
子组件拿到父组件创建的 ref ,绑定到自己的某一个元素中。
-
示例
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useContext
React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context。
作用
- 定义全局状态,进行全局状态管理。
- 为了能够进行数据的绑定。 当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。
- Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式的能力。
- 可以帮助我们跨越组件层级直接传递变量,实现数据共享。一般用于祖孙传值。一般用于统一下发,权限,当前所在地,主题色等。
- Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。
使用方法
const MyContext = React.createContext(initialValue);一个 Context 是从某个组件为根组件的组件树上可用的, 所以,我们需要有 API 能够创建一个 Context,这就是 React.createContext API。const value = useContext(MyContext);组件的 API 签名。- 使用 useContext 在改变一个数据时,是通过自己逐级查找对比改变的数据,进行渲染,而不是通过数据响应式来监控变量的。
示例
import React, {createContext, useContext, useState} from 'react';
// 创建 Context
const CountContext = createContext(0)
// 孙子组件
const Grandson = () => {
const {count, setCount} = useContext(CountContext)
console.log(useContext(CountContext))
return (
<div>
{count}
{/* 在孙子组件中,使用爷爷组件定义的变量 count,并进行 +1 操作 */}
<button onClick={() => setCount(e => e + 1)}>孙子点击事件</button>
</div>
)
}
// 子组件
const Child = () => {
// 通过 useContext 把刚刚创建好的 CountContext 作为参数传进去,并读取 count 值
const {count, setCount} = useContext(CountContext)
return (
<div>
{count}
{/* 在子组件中,使用父组件定义的变量 count,并进行 -1 操作 */}
<button onClick={() => setCount(e => e - 1)}>子组件点击事件</button>
<Grandson />
</div>
)
}
// 父组件
const HooksUseContext = () => {
const [count, setCount] = useState(0)
return (
<div>
{count}
<button onClick={() => setCount(e => e + 1)}>点击</button>
{/* 使用 CountContext.Provider 包裹需要接收参数的子组件,并通过 value 传值 */}
<CountContext.Provider value={{count, setCount}}>
<Child />
</CountContext.Provider>
</div>
);
};
export default HooksUseContext;
useReducer
作用
- useState 的替代方案。
使用方法
const [state, dispatch] = useReducer(reducer, initialArg, init);- useReducer 接收两个参数:第一个参数是一个 reducer 函数;第二个参数是初始化的 state,返回值为最新的 state 和 dispatch 函数(用来触发 reducer 函数,计算对应的 state。)
- reducer 本质是一个纯函数,没有任何 UI 和副作用,这意味着相同的输入(state,action),reducer 函数无论执行多少遍,始终会返回相同的输出。
- useState的替代方案,它接收一个形如 ( state , action ) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
- 在某些场景下,useReducer 会比 useState 更适用,例如:state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数。
- 这一次彻底搞定useReducer-使用篇 - 知乎 (zhihu.com)
示例
import React, {useReducer} from 'react';
const HooksUseReducer = () => {
// 第一个参数:应用的初始化
const initialState = {count: 0}
/**
* 第二个参数:state 的 reducer 处理函数
* @param state 默认初始值
* @param action 传递的事件名称
* @returns {{count: *}|{count: number}}
*/
const reducer = (state, action) => {
console.log(state, action, '===')
switch (action.type) {
case 'decrement':
return {count: state.count - 1}
case 'increment':
return {count: state.count + 1}
default:
throw new Error()
}
}
/**
* 返回值:最新的 state 和 dispatch 函数
* state:初始值
* dispatch:函数
*/
const [state, dispatch] = useReducer(reducer, initialState)
console.log(state)
return (
<div>
{/* useReducer 会根据 dispatch 的 action,返回最终的 state,并触发 reducer */}
count:{state.count}
{/* dispatch 用来接收一个 action 参数 [reducer 中的 action],用来触发 reducer 函数,更新最新的状态 */}
<button onClick={() => dispatch({type: 'decrement'})}>点击-1</button>
<button onClick={() => dispatch({type: 'increment'})}>点击+1</button>
</div>
);
};
export default HooksUseReducer;
React.memo
使用方法
-
高阶组件定义:参数为组件,返回值为新组件的函数。
-
默认会对 props 做一层浅比较,如果 props 不变,那么就不会重新渲染。
-
可接收第二个参数:
- 默认情况下其只会对复杂对象做浅层对比, 如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。第二个参数是一个函数,返回 true 不渲染,false 渲染。
- 可以做渲染劫持,自定义逻辑做劫持。
特点
- React.memo 仅检查 props 变更。 如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
示例
import React, {memo, useState} from 'react';
/**
* 只有点击按钮一时,组件才会渲染
* 因为,经过 memo 高阶组件包裹的函数,只有 props 发生变化时,才会更新
* Child 组件接收的是 count 值,一旦 count 的值,有所改变,便会重新渲染组件
* 而 count2 未传给 Child 组件值,所以,当 count2 发生改变时,不会引起 Child 组件的渲染
* @type {React.NamedExoticComponent<object>}
*/
const Child = memo(() => {
console.log('Child组件更新啦')
return <div>Child</div>
})
/**
* 未经 memo 组件包裹的组件,只要父组件发生变化,但是子组件数据没有发生变化,也会重新渲染组件
* @returns {JSX.Element}
* @constructor
*/
const GrandSon = () => {
console.log('GrandSon组件更新啦')
return <div>GrandSon</div>
}
const HooksReactMemo = () => {
const [count, setCount] = useState(0)
const [count2, setCount2] = useState(0)
return (
<div>
count:{count}
<button onClick={() => setCount(e => e + 1)}>点击事件</button>
{count2}
<button onClick={() => setCount2(e => e + 1)}>点击事件</button>
<GrandSon count={count} />
<Child count={count} />
</div>
);
};
export default HooksReactMemo;
劫持渲染
父组件:
import React, {useState} from 'react';
import List from "./list";
const Homework = () => {
const [cards, setCards] = useState([{name: '測試'}])
const add = () => {
setCards([...cards, {name: '测试'}])
}
return (
<div>
<button onClick={add}>添加</button>
<List cards={cards}/>
</div>
);
};
export default Homework;
子组件:
import React from 'react';
import './index.css'
const List = ({cards}) => {
return (
<div className={'card'}>
{
cards.map((item, index) => <div>{item.name} -- {index + 1}</div>)
}
</div>
);
};
export default React.memo(List, (preProps, newProps) => {
/**
* prevProps:旧的值
* nextProps:新的值
* 如果把 nextProps 传入 render 方法的返回结果与
* 将 prevProps 传入 render 方法的返回结果一致则返回 true,
* 否则返回 false
* 每次渲染前,或者说,每次 props 更新后,要渲染页面前,
* 在这里做一层拦截
*/
return newProps.cards.length > 20 && newProps.cards.length < 30
});
hooks 使用 form
- 经过 Form.create() 包装过的组件自带 form 属性,使用 connect 连接 model 层。
function mapStateToProps({ carInStore, loading }) {
return {
data: carInStore,
loading: loading.effects['carInStore/fetchList'],
};
}
const Index = (props) => {
const { form, loading, dispatch, data: {dataList, showConfirmModal } } = props;
return (
<div> hooks 使用 form </div>
);
};
export default Form.create()(connect(mapStateToProps)(Index));
自定义 hooks
使用方法
- 声明一个以 use 开头的函数,比如:useCounter。
与普通函数的区别
- 在语义上是有区别的,区别就在于函数中有没有用到其它的 Hooks。
- 就是说,如果你创建了一个 useXXX 的函数,但是,内部并没有用到任何其它的 hooks,name这个函数就不是一个 Hook,而是一个普通函数。但是如果用了其它 Hooks,那么它就是一个 Hook。
原则
- 名字一定是以 use 开头的函数, 不遵循这个约定的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
- 函数内部一定调用了其他的 Hooks,可以是内置的 Hooks,也可以是其他自定义的 Hooks。 这样才能够让组件去刷新,或者去产生副作用。
优势
- 一方面能让这个逻辑得到重用,另一个方面也能让代码更加语义化,并且易于理解和维护。
- 自定义 hook:
import { useState, useCallback }from 'react';
function useCounter() {
// 定义 count 这个 state 用于保存当前数值
const [count, setCount] = useState(0);
// 实现加 1 的操作
const increment = useCallback(() => setCount(count + 1), [count]);
// 实现减 1 的操作
const decrement = useCallback(() => setCount(count - 1), [count]);
// 重置计数器
const reset = useCallback(() => setCount(0), []);
// 将业务逻辑的操作 export 出去供调用者使用
return { count, increment, decrement, reset };
}
- 在组件中使用自定义 hook
import React from 'react';
function Counter() {
// 调用自定义 Hook
const { count, increment, decrement, reset } = useCounter();
// 渲染 UI
return (
<div>
<button onClick={decrement}> - </button>
<p>{count}</p>
<button onClick={increment}> + </button>
<button onClick={reset}> reset </button>
</div>
);
}