hooks 是篇带函数组件独有的。在不编写 class 的全上情况下使用 state 以及其他的 React 特性。 只能在函数组件的篇带顶级作用域使用;只能在函数组件或者其他 Hooks 中使用。 hooks 使用时必须确保: 使用 Hooks 的全上一些特性和要遵循某些规则。 React 官方提供了一个 ESlint 插件,篇带专门用来检查 Hooks 是全上否正确被使用。 安装插件:npm install eslint-plugin-react-hooks --save-dev 在 ESLint 配置文件中加入两个规则:rules-of-hooks和exhaustive-deps。篇带 { "plugins": [ // ... "react-hooks" ],全上 "rules": { // ... // 检查 Hooks 的使用规则 "react-hooks/rules-of-hooks": "error", // 检查依赖项的声明 "react-hooks/exhaustive-deps": "warn" } } import React, { userState } from react function Example() { // 声明一个叫count的state变量,初始值为0 // 可以使用setCount改变这个count const [ count,篇带 setCount ] = useState(0) return ( You clicked { count} times Click me ); } useState 的入参也可以是一个函数。当入参是全上一个函数的时候,这个函数只会在这个组件初始化渲染的篇带时候执行。 const [ count,全上 setCount ] = useState(() => { const initialCount = someExpensiveComputation(props) return initialCount }) setState 也可以接收一个函数作为参数:setSomeState(prevState => { })。 // 常见写法 const handleIncrement = useCallback(() => setCount(count + 1),篇带 [count]) // 下面这种性能更好些 // 不会每次在 count 变化时都使用新的。 // 从而接收这个函数的组件 props 就认为没有变化,避免可能的云南idc服务商性能问题 const handleIncrement = useCallback(() => setCount(q => q + 1), []) useEffect 会在每次 DOM 渲染后执行,不会阻塞页面渲染。 在页面更新后才会执行。即:每次组件 render 后,判断依赖并执行。 它同时具备 componentDidMount 、 componentDidUpdate 和 componentWillUnmount 三个生命周期函数的执行时机。 useEffect 共两个参数: callback 和 dependences 。规则如下: 依赖项中定义的变量一定是会在回调函数中用到的,站群服务器否则声明依赖项其实是没有意义的。 依赖项一般是一个常量数组,而不是一个变量。因为一般在创建 callback 的时候,你其实非常清楚其中要用到哪些依赖项了。 React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。 如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。这是一个刚开始使用 Hooks 时很容易导致 Bug 的地方。 在页面更新之后会触发这个方法 : import React, { useState, useEffect } from react; function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${ count} times`; }); return ( You clicked { count} times Click me ); } 如果想针对某一个数据的改变才调用这个方法,需要在后面指定一个数组,数组里面是需要更新的那个值,它变了就会触发这个方法。数组可以传多个值,一般会将 Effect 用到的高防服务器所有 props 和 state 都传进去。 // class 组件做法 componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${ this.state.count} times` } } // 函数组件 hooks做法:只有 count 变化了才会打印出 aaa userEffect(() => { document.title = `You clicked ${ count} times` }, [count]) 如果我们想只在 mounted 的时候触发一次,那我们需要指定后面的为空数组,那么就只会触发一次,适合我们做 ajax 请求。 userEffect(() => { console.log(mounted) }, []) 如果想在组件销毁之前执行,那么我们就需要在 useEffect 里 return 一个函数。 useEffect(() => { console.log("mounted"); return () => { console.log(unmounted) } }, []); 示例:在 componentDidMount 中订阅某个功能,在 componentWillUnmount 中取消订阅。 // class组件写法 class Test extends React.Component { constructor(props) { super(props) this.state = { isOnline: null } this.handleStatusChange = this.handleStatusChange.bind(this) } componentDidMount() { ChatApi.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ) } componentWillUnmount() { ChatApi.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ) } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }) } render() { if (this.state.isOnline === null) { return loading... } return this.state.isOnline ? Online : Offline } } // 函数组件 hooks写法 function Test1(props) { const [ isOnline, setIsOnline ] = useState(null) useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline) } ChatApi.subscribeToFriendStatus(props.friend.id, handleStatusChange) // 返回一个函数来进行额外的清理工作 return function cleanup() { ChatApi.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange) } }) if (isOnline === null) { return loading... } return isOnline ? Online : Offline } useLayoutEffect 的用法跟 useEffect 的用法是完全一样的,它们之间的唯一区别就是执行的时机。 useLayoutEffect 会阻塞页面的渲染。会保证在页面渲染前执行,也就是说页面渲染出来的是最终的结果。 如果使用 useEffect ,页面很可能因为渲染了2次而出现抖动。 绝大多数情况下,使用 useEffect 即可。 useContext 可以很方便的去订阅 context 的改变,并在合适的时候重新渲染组件。 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。 context 基本示例: // 因为祖先组件和子孙组件都用到这个ThemeContext, // 可以将其放在一个单独的js文件中,方便不同的组件引入 const ThemeContext = React.createContext(light) class App extends React.Component { render() { return ( ) } } // 中间层组件 function Toolbar(props) { return ( ) } class ThemedButton extends React.Component { // 通过定义静态属性 contextType 来订阅 static contextType = ThemeContext render() { return } } 针对函数组件的订阅方式: function ThemedButton() { // 通过定义 Consumer 来订阅 return ( { value => ) } 使用 useContext 来订阅: function ThemedButton() { const value = useContext(ThemeContext) return } 在需要订阅多个 context 的时候,对比: // 传统的实现方法 function HeaderBar() { return ( { user => { notification => Welcome back, { user.name}! You have { notifications.length} notifications. } } ) } // 使用 useContext function HeaderBar() { const user = useContext(CurrentUser) const notifications = useContext(Notifications) return ( Welcome back, { use.name}! You have { notifications.length} notifications. ) } useReducer 用法跟 Redux 非常相似,当 state 的计算逻辑比较复杂又或者需要根据以前的值来计算时,使用这种 Hook 比 useState 会更好。 function init(initialCount) { return { count: initialCount } } function reducer(state, action) { switch(action.type) { case increment: return { count: state.count + 1 } case decrement: return { count: state.count - 1 } case reset: return init(action.payload) default: throw new Error() } } function Counter({ initialCount }) { const [state, dispatch] = useReducer(reducer, initialCount, init) return ( <> Count: { state.count} ) } 结合 context API,我们可以模拟 Redux 的操作: const TodosDispatch = React.createContext(null) const TodosState = React.createContext(null) function TodosApp() { const [todos, dispatch] = useReducer(todosReducer) return ( ) } function DeepChild(props) { const dispatch = useContext(TodosDispatch) const todos = useContext(TodosState) function handleClick() { dispatch({ type: add, text: hello }) } return ( <> { todos} ) } useCallback 和 useMemo 设计的初衷是用来做性能优化的。useCallback 缓存的是方法的引用。useMemo 缓存的则是方法的返回值。使用场景是减少不必要的子组件渲染。// useCallback function Foo() { const [ count, setCount ] = useState(0) const memoizedHandleClick = useCallback( () => console.log(`Click happened with dependency: ${ count}`), [count], ) return } // useMemo function Parent({ a, b}) { // 当 a 改变时才会重新渲染 const child1 = useMemo(() => // 当 b 改变时才会重新渲染 const child2 = useMemo(() => return ( <> { child1} { child2} ) } 若要实现 class 组件的 shouldComponentUpdate 方法,可以使用 React.memo 方法。 区别是它只能比较 props ,不会比较 state。 const Parent = React.memo(({ a, b}) => { // 当 a 改变时才会重新渲染 const child1 = useMemo(() => // 当 b 改变时才会重新渲染 const child2 = useMemo(() => return ( <> { child1} { child2} ) }) // class组件获取ref class Test extends React.Component { constructor(props) { super(props) this.myRef = React.createRef() } componentDidMount() { this.myRef.current.focus() } render() { return } } // 使用useRef function Test() { const myRef = useRef(null) useEffect(() => { myRef.current.focus() }, []) return } useRef 值的变化不会引起组件重绘,可以存一些跟界面显示无关的变量。 函数式组件不能设置 ref ,想保存其中的某些值,可以通过 React.forwardRef。 import React, { useState, useEffect, useRef, forwardRef } from react export default function Home(props) { const testRef = useRef(null) useEffect(() => { console.log(testRef.current) }, []) return ( ) } /* eslint-disable react/display-name */ // const Test = forwardRef((props, ref) => ( // // // { props.children} // // )) const Test = forwardRef(function Test(props, ref) { const [count, setCount] = useState(1) useEffect(() => { ref.current = { count, setCount } }) return ( ) }) 自定义 Hook 是一个函数,但是名称必须是以 use 开头,函数内部可以调用其他的 Hook。 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。 跟普通的 hook 一样,只能在函数组件或者其他 Hooks 中使用。简介
ESlint
useState
useEffect
useLayoutEffect
useContext
useReducer
useCallback / useMemo / React.memo
return
自定义hook