react hooks 快速上手

react hooks 使用教程

本教程使用简单代码介绍了 react 的 useState,useEffect,useContext,useReducer, useRef,useMemo,useCallback 以及自定义hooks 的用法。

useState

向组件添加一个 状态变量,进行状态管理。

js 复制代码
const [state, setState] = useState(initialState)
  • initialState: 你希望 state 初始化的值。它可以是任何类型的值,但对于函数有特殊的行为。在初始渲染后,此参数将被忽略。 如果传递函数作为 initialState,则它将被视为 初始化函数。它应该是纯函数,不应该接受任何参数,并且应该返回一个任何类型的值。当初始化组件时,React 将调用你的初始化函数,并将其返回值存储为初始状态。
js 复制代码
function App() {
    const [count, setCount] = useState(0)

    const handlePlus = () => {
        setCount(count + 1)
    }

    // setCount 传入函数,第一个参数为 useState 的旧值
    const handleMinus = () => {
        setCount(count => count - 1)
    }

    return (
        <>
            <div>{count}</div>
            <button onClick={handlePlus}>+</button>
            <button onClick={handleMinus}>-</button>
        </>

    )
}

useEffect

useEffect(setup, dependencies?)

dependencies 传入空数组只会在组件更新时触发一次回调函数, setup 函数 在每次依赖项变更重新渲染后, setup 函数可以选择性返回一个 清理(cleanup) 函数,React 将首先使用旧值运行 cleanup 函数

js 复制代码
useEffect(() => {
    console.log('useEffect')
}, []);

数组中传入具体的状态,当状态变化时触发回调函数

js 复制代码
useEffect(() => {
    console.log('useEffect')
}, [count]);

setup 函数返回的函数称为 cleanup 清理函数

js 复制代码
useEffect(() => {
    console.log('useEffect')

    return () => {
        console.log('clear')
    }
}, [count]);

useContext

通过父组件给后代组件传递数据时,如果嵌套的层级太深,可以通过在组件的最顶级调用 useContext 来读取和订阅 context。

jsx 复制代码
// 创建 context
const ThemeContext = createContext(null)

function App() {
    return (
        // 顶层组件传递 context 数据
        <ThemeContext.Provider value={{color: 'green'}}>
            <ChildComponent></ChildComponent>
        </ThemeContext.Provider>
    )
}

function ChildComponent() {
    // 内部组件获取 context 数据
    const theme = useContext(ThemeContext)
    console.log(theme) // {color: 'green'}

    return (
        <div>Child Component</div>
    )
}

以上代码演示了如何通过在顶层组件设置 context 并且传递数据,内部的组件如何获取数据。

jsx 复制代码
// 创建 context
const ThemeContext = createContext(null)

function App() {
    const [theme, setTheme] = useState('green')

    const switchTheme = () => {
        setTheme(theme === 'green' ? 'pink' : 'green')
    }

    return (
        // 传递 context 数据
        <ThemeContext.Provider value={{color: theme}}>
            <div>
                <button onClick={ switchTheme }>switch theme</button>
            </div>
            <ChildComponent></ChildComponent>
        </ThemeContext.Provider>
    )
}

function ChildComponent() {
    const theme = useContext(ThemeContext)
    console.log(theme)

    return (
        <div style={theme}>Child Component</div>
    )
}

通过结合 useState 可以更新 context 并且传递给后代,这里演示了如何 通过 context 全局更新主题颜色。

useReducer

对于复杂的状态设置和管理。

scss 复制代码
const [state, dispatch] = useReducer(reducer, initialArg, init?)
  • reducer: 用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
  • initialArg: 用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
  • 可选参数 init: 用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg

通过不同的 reducer type 执行不同的操作

jsx 复制代码
const initialState = {
    count: 0,
    age: 18,
    // 一些其他属性
}

const countReducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return {
                ...state,
                count: state.count + 1,
            }
        case 'decrement':
            return {
                ...state,
                count: state.count - 1,
            }
        default:
            return state;
    }
}

export function CountProvider() {

    const [state, dispatch] = useReducer(countReducer, initialState)

    const increment = () => {
        dispatch({type: 'increment'})
    }

    const decrement = () => {
        dispatch({type: 'decrement'})
    }

    return (
        <div>
            <h1>{state.count}</h1>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
        </div>
    )
}

useRef

用于在重新渲染中,去存储易变数据。数据存储在 current 属性上,常用于 DOM 访问。(修改 ref.current 不会触发重新渲染)

csharp 复制代码
const ref = useRef(initialValue)
  • initialValue: ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。

返回值: useRef 返回一个只有一个属性的对象:

  • current:初始值为传递的 initialValue。之后可以将其设置为其他值。如果将 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性。

在后续的渲染中,useRef 将返回同一个对象。

jsx 复制代码
// useRef 操作 dom 案例
export function RefDemo() {
    const inputRef = useRef(null)

    const handleClick = () => {
        inputRef.current.focus()
    }

    return (
        <div>
            <input ref={inputRef}/>
            <button onClick={handleClick}>
                聚焦输入框
            </button>
        </div>
    )
}

useMemo

在每次重新渲染的时候能够缓存计算的结果

js 复制代码
const cachedValue = useMemo(calculateValue, dependencies)
  • calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。
  • dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 [dep1, dep2, dep3] 这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
js 复制代码
// 省略部分代码。。。
const [count, setCount] = useState(0)

const doubleCount = useMemo(() => {
    console.log('in use Memo')
    return count * 2
}, [count])

useMemo 在多次重新渲染中缓存了 calculation 函数计算的结果直到依赖项的值发生变化。

另外你可以使用 memo 来优化你的代码,它允许你的组件在 props 没有改变的情况下跳过重新渲染。

ini 复制代码
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
jsx 复制代码
const Greeting = memo(function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
});

export default Greeting;

useCallback

允许你在多次渲染中缓存函数。

ini 复制代码
const cachedFn = useCallback(fn, dependencies)
  • fn:想要缓存的函数。此函数可以接受任何参数并且返回任何值。在初次渲染时,React 将把函数返回给你(而不是调用它!)。当进行下一次渲染时,如果 dependencies 相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数。你可以自己决定何时调用以及是否调用。

  • dependencies:有关是否更新 fn 的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。如果你的代码检查工具 配置了 React,那么它将校验每一个正确指定为依赖的响应式值。依赖列表必须具有确切数量的项,并且必须像 [dep1, dep2, dep3] 这样编写。React 使用 Object.is 比较每一个依赖和它的之前的值。(简言之就是依赖项列表中的变量需要被想要缓存的函数内部使用)

  • 返回值: 在初次渲染时,useCallback 返回你已经传入的 fn 函数 在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn。

jsx 复制代码
export function UseCallbackDemo() {
    const [state, setState] = useState('on')
    const [count, setCount] = useState(0)

    const handleAdd = () => {
        setCount((count) => count + 1)
    }

    const cacheFn = useCallback(() => {
        console.log(state)
    }, [state]);

    const handleChange = () => {
        setState(state === 'on' ? 'off' : 'on')
    }

    return (
        <div>
            <button onClick={handleAdd}>add</button>
            <h3>{count}</h3>
            <button onClick={handleChange}>change</button>
            <Child handleClick={cacheFn}></Child>
        </div>
    )
}

// eslint-disable-next-line react/prop-types
function Child({handleClick}) {
    return (
        <div>
            <button onClick={() => handleClick()}>click</button>
        </div>
    )
}

点击 'add' 会触发父组件的重新渲染,但点击子组件的 Child 的 'Click' 发现打印的内容没有变化。点击 'Change' 才会改变子组件的 handleClick 的打印结果。因为 useCallback 的依赖项里面包含 state 状态。

自定义 hook

自定义一个 useLocalStorage,该 Hook 用于将状态与 localStorage 同步,实现数据的持久化存储

js 复制代码
export const useLocalStorage = (key, initialValue) => {
    const [stateValue, setStateValue] = useState(() => {
        try {
            const item = localStorage.getItem(key);
            // 如果存在则解析,否则判断 initialValue 是否为函数
            return item ? JSON.parse(item) : (typeof initialValue === 'function' ? initialValue() : initialValue);
        } catch (error) {
            console.log(error)
            return (typeof initialValue === 'function' ? initialValue() : initialValue);
        }
    })

    const setStorage = (value) => {
        let valueToStore;
        if (typeof value === 'function') {
            valueToStore = value(stateValue)
        } else {
            valueToStore = value
        }
        setStateValue(valueToStore)
        localStorage.setItem(key, JSON.stringify(valueToStore))
    }

    return [stateValue, setStorage]
}
相关推荐
Moment1 小时前
从方案到原理,带你从零到一实现一个 前端白屏 检测的 SDK ☺️☺️☺️
前端·javascript·面试
野生的程序媛2 小时前
重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)
前端·javascript·vue.js
鱼樱前端2 小时前
Vue 2 与 Vue 3 响应式原理详细对比
javascript·vue.js
codingandsleeping2 小时前
前端工程化之模块化
前端·javascript
CodeCraft Studio2 小时前
报表控件stimulsoft操作:使用 Angular 应用程序的报告查看器组件
前端·javascript·angular.js
阿丽塔~2 小时前
面试题之vue和react的异同
前端·vue.js·react.js·面试
Liigo3 小时前
初次体验Tauri和Sycamore(3)通道实现
javascript·rust·electron·tauri·channel·sycamore
烛阴3 小时前
JavaScript 性能提升秘籍:WeakMap 和 WeakSet 你用对了吗?
前端·javascript
专注VB编程开发20年4 小时前
JS采集数据爬虫-Fetch API 和 XMLHttpRequest 有什么区别?
开发语言·javascript·爬虫·js
鱼樱前端4 小时前
Vue 2 与 Vue 3 语法区别完整对比
前端·javascript·vue.js