拥抱 React Hooks

React Hooks

为何须要Hooks?

咱们知道,React 提供的单向数据流以及组件化帮助咱们将一个庞大的项目变为小型、独立、可复用的组件。但有时,咱们没法进一步拆分很复杂的组件,由于它们内部的逻辑是有状态的,没法抽象为函数式组件。因此有时咱们可能会写出很是不适合复用性开发的:react

  • 巨大的组件 难以重构
  • 重复的逻辑 须要在多个组件的多个生命周期中写重复的代码
  • 复杂的应用模式 相似于 render props 于 高阶组件

但谢天谢地,Hooks 的出现,让咱们把组件内部的逻辑组织成为了可复用的隔离单元api

Hooks 要解决的问题:

跨组件地复用包含状态的逻辑,经过 Hooks 能够将含有 state 的逻辑从组建抽象出来,同时也能够帮助咱们在不重写组件结构的状况下复用逻辑。Hooks 通常是用于函数式组件的,在类class组件中无效。让咱们根据代码的做用将它们拆分,而不是生命周期。简而言之, Hooks 实现了咱们在函数式组件中使用状态变量相似于生命周期的操做。数组

使用 Hooks 的语法规则

  • 只能在顶层调用钩子。不在循环、控制流和嵌套的函数中调用钩子。
  • 只能从React的函数式组件中调用钩子。不在常规的JS函数中调用钩子。

建立Hooks

  • 使用useState建立Hook
import {useState} from 'react';

function hooks(){
    // 声明一个名为 count 的新状态变量
    const [count, setCount] = useState(0);
    // 第二个参数 setCount 为一个能够更新状态的函数
    // useState 的参数即为初始值
    
    return (
        <div>
        	<p>当前的状态量为: {count}</p>
            <button onClick={() => setCount(count + 1)}>点击加一</button>
        </div>
    )
}
复制代码
  • 使用 useEffect 来执行相应操做
import {useState, useEffect} from 'react';

function hooks(){
    const [count, setCount] = useState(0);
    // 相似于 componentDidMount 和 componentDidUpdate
    // 在 useEffect 中可使用组建的 state 和 props
    // 在每次渲染后都执行 useEffect
    useEffect(() => {
        window.alert(`You have clicked ${count} times`);
    })
    return (
        <div>
        	<p>当前的状态量为: {count}</p>
            <button onClick={() => setCount(count + 1)}>点击加一</button>
        </div>
    )
}
复制代码

钩子是独立的

咱们在两个不一样的组件使用同一个钩子,他们是相互独立的,甚至在一个组件使用两个钩子他们也是相互独立的。浏览器

React如何保证useState相互独立

React 实际上是根据useState传出现的顺序来保证useState之间相互独立。缓存

// 首次渲染
const [num, setNum] = useState(1); // 将num初始化为1
const [str, setStr] = useState('string'); // 将str初始化为'string'
const [obj, setObj] = useState({id:1}); // ....
// 第二次渲染
const [num, setNum] = useState(1); // 读取状态变量num的值, 此时传入的参数已被忽略,下同
const [str, setStr] = useState('string'); // 读取状态变量str的值
const [obj, setObj] = useState({id:1}); // ....
复制代码

同时正是因为根据顺序保证独立,因此 React 规定咱们必须把 hooks 写在最外层,而不能写在条件语句之中,来确保hooks的执行顺序一致,若要进行条件判断,咱们应该在 useEffect 的函数中写入条件闭包

Effect Hooks

useEffect 来传递给 React 一个方法,React会在进行了 DOM 更新以后调用。咱们一般将 useEffect 放入组件内部,这样咱们能够直接访问 state 与 props。记得,useEffect 在每次 render 后都要调用。异步

须要清理的Effect

咱们有时须要从外部数据源获取数据,此时咱们就要保证清理Effect来避免内存泄露 ,此时咱们须要在 effect 中返回一个函数来清理它, React 会在组件每次接触挂载的时候清理。一个比较使用的场景就是咱们在 useEffect中若执行了异步请求,因为异步的时间不肯定性,咱们很须要在执行下一次异步请求时先结束上一次的请求,所以咱们就须要清理。async

useEffect(() => {
    let canceled = false;
    const getData = async () => {
        const res = await fetch(api);
        if(!canceled) {
            // 展现 res
        }
    }
    
    getData();
    
    // return 的即为咱们的清理函数
    return () => {
        canceled = true;
    }
});
复制代码

此时咱们在进行从新渲染时,就能够避免异步请求带来的竞态问题,从而避免数据的不稳定性。函数

配置根据条件执行的Effect

咱们能够给useEffect传入第二个参数只有当第二个参数(数组)里的全部的state 值发生变化时,才从新执行Effect组件化

useEffect(() => {
    window.alert(`you had clicked ${count} times`);
}, [count]); //只有当 count 发生变化时才会从新执行effect
复制代码

在函数式组件使用实例

因为函数式组件中没有 this ,因此咱们没法使用ref,但hooks帮助咱们解决了这个问题,他提供了useRef方法来为咱们建立一个实例,而传入的参数会被挂载在这个实例的.current属性上,返回的实例会持续到整个生命周期结束为止。

function RefExample() {
    const ref1 = useRef(null);
    return (
    	<div>
            <input ref={ref1} type="text" />
            <button onClick={() => {ref1.current.focus()}}
    	</div>
    )
}
复制代码

类型的Hooks

若是比起上面的状态变量类型,你更想要使用 Redux 类型的状态管理,OK,React 也给咱们提供了useReducer这个方法。做为useState 的一种替代,咱们可使用dispatch方法来改变状态变量。

// 初始化的状态变量
const initState = {count:0};
// 编写 reducer 处理函数
function reducer(state, action) {
    switch(action.type) {
        case 'increment': return {count: state.count + 1};
        case 'decrement': return {count: state.count - 1};
    }
}

function counter({initState}) {
    const [state, dispatch] = useReducer(reducer, initState);
    return (
    <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
   	</div>
    )
}
复制代码

回调形式的Hooks

咱们能够经过监听状态变量并在变换后执行回调函数来执行 Effect ,此时你可能会问,为何使用 Hooks 会使用这么多的 inline 函数,岂不是很影响性能? 谢天谢地,JavaScript 中的闭包函数的性能十分的快,它帮助了咱们不少。回调形式的 Hooks 有两种,useCallbackuseMemo.

两者的转换关系为:

useCallback(fn, inputs) === useMemo(() => fn, inputs)

useCallback是如何帮助咱们提高性能的呢? 实际上,它实际上是缓存了每次渲染时的 inline 回调函数的实例,以后不管是配合shouldComponentUpdate 或者是 React.memo都可以达到减小没必要要的渲染的做用。这也提示咱们,React.memoReact.useCallback通常是配合使用,缺了其一均可能没法达到提高性能的功效。

下面以一个表单组件表示使用方法

function FormComponent() {
    const [text, setText] = useState(' ');
    
    const handleSubmit = useCallback(() => {
        console.log(`new test is ${text}`);
    }, [text]);
    
    return (
    	<div>
        	<input value={text} onChange={(e) => setText(e.target.value)} />
            <BigTree onSubmit={handleSubmit} /> // 巨大无比的组件,不优化卡的不行
        </div>
    )
}
复制代码

但此时有一个很严重的问题,就是咱们的 BigTree 依赖于一个太容易变化的 state, 只要咱们在input框随意输入, BigTree 就会从新渲染好屡次来获取最新的callback,此时这个callback就没法使用缓存了。

一个解决办法是咱们定义一个新的实例,这个实例只有在 re-render 时才会更新最新的值,这样咱们就能够不根据一个常常变换的state,而是根据一个在 useLayoutEffect中更新的ref实例来更新。

function FormComponent() {
    const [text, setText] = useState(' ');
    const textRef = useRef();
    
    useLayoutEffect(() => {
        textRef.current = text;
    })
    
    const handleSubmit = useCallback(() => {
        console.log(`new test is ${text}`);
    }, [textRef]); // 只根据 textRef 的变化而产生变化,并不会在 text 改变就变化
    
    return (
    	<div>
        	<input value={text} onChange={(e) => setText(e.target.value)} />
            <BigTree onSubmit={handleSubmit} /> // 巨大无比的组件,不优化卡的不行
        </div>
    )
}
复制代码

Hooks的多重 Effect 更新场景

useLayoutEffect

DOM 突变以后,从新绘制以前同步触发

它与 useEffect 的做用相同,都是用来执行反作用的,但不一样的是,它会在全部的 DOM 变动结束后同步地调用 effect。一个与 useEffect很大的区别是,useLayoutEffect是同步地,而useEffect是异步的,在浏览器从新绘制页面布局前,useLayoutEffect内部的更新将会同步刷新,但官方给出的建议是尽可能使用useEffect来避免阻塞视觉更新。

Hooks 的好处

  • 避免了咱们在复用含状态组件(classes) 时使用 render props高阶组件时产生的

夸张的层级嵌套。

  • 防止咱们为了实现功能而在生命周期函数中写入了大量的重复的代码。
  • classes 中的 this 指向十分的迷惑。
相关文章
相关标签/搜索