乐趣区

关于前端:React-Hooks

React Hooks 是 React v16.8 中新增加的个性,这个个性次要是对函数型组件的性能进行加强,让函数型组件能够做类组件做的事件。

这个篇文章次要蕴含三个局部:

  1. 什么是 react hooks

    1. react hooks 次要蕴含哪些性能,要解决哪些问题。
  2. react hooks 的应用

    1. react hooks 到底是什么
    2. 这些函数要在什么场景下应用
  3. 自定义 react hooks

    1. 通过本人定义函数,如何将组件外部的公共逻辑提取进去

1. 什么是 React Hooks

1.react hooks 性能介绍

对函数组件进行加强,让函数组件能够贮存状态,能够领有解决副作用的能力,让开发者在不应用类组件的状况下,实现雷同的性能。

副作用:在组件中不是将数据转换为视图的代码被称为副作用。

比方:获取 DOM 元素、为 DOM 元素增加事件、设置定时器以及发送 Ajax 申请等,这都属于副作用。

2. 类组件的有余

  1. 短少逻辑复用机制

    1. 为了复用逻辑减少无理论渲染成果的组件,减少了组件层级显示非常臃肿,减少了调试的难度以及运行效率的升高。
  2. 类组件常常会变得很简单难以保护

    1. 将一组相干的业务逻辑拆分到多个生命周期函数中,例如:1. 在组件挂载时要做些什么 2. 更新实现之后要做些什么
    2. 在一个生命周期函数内存在多个不相干的业务逻辑
    3. 类成员的办法不能保障 this 指向的正确性,例如:在申明一些函数要应用 bind 进行绑定,或者嵌套函数来解决 this 指向的问题

2. React Hooks 的应用

Hooks 意为钩子,React Hooks 就是一堆钩子函数,React 通过这些钩子函数对函数组型件进行加强,不同的钩子函数提供了不同的性能。

react hooks 提供的钩子函数有:

  • useState()
  • useEffect()
  • useReducer()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()

1.useState

用于为函数组件引入状态,在现有的认知外面,在函数执行结束之后该变量就会被开释掉,所以函数型组件本来是不能够保留组件数据的,useState 用来解决这个问题

import React, {useState} from 'react';

function App () {
  // useState 返回一个数组,count: 状态 setCount: 设置状态的办法
  const [count, setCount] = useState(0);
  return <div>
     <span>{count}</span>
     <button onClick={() => setCount(count => count + 1)}>+1</button>
  </div>
}

应用 setCount 办法设置 count 的值后,组件会被从新渲染,而渲染后 count 的状态还在。

  • useState 应用细节

    1. 接管惟一的参数即状态初始值,初始值能够是任意数据类型。
    2. 返回为数组,数组中存储状态值和更新状态值的办法,办法名称约定以 set 结尾,前面加上状态的名称。
    3. useState 办法能够被调用屡次,用以保留不同的状态值。
    4. 参数能够是一个函数,函数返回什么初始状态值就是什么,函数只会被调用一次,用在初始值是动静值的状况下。
import React, {useState} from 'react';

function UseState (props) {console.log('渲染即执行');
  // 传入一个办法
  const [count, setCount] = useState(() => {console.log('这个地位只会被执行一次');
    return props.count || 0;
  });
  // 能够接管任意数据类型为状态值
  const [person, setPerson] = useState({name: 'howie', age: 20});
  return <div>
    <span>{count} {person.name} {person.age}</span>
    <button onClick={() => setCount(count + 1)}>plus</button>
    <button onClick={() => setPerson(() => ({...person, name: 'zs'}))}>change person</button>
  </div>
}

export default UseState;
  • 设置状态的细节

    1. 设置状态值的办法的参数,能够是一个值也能够是一个函数
    2. 设置状态值的办法自身是异步的
import React, {useState} from 'react';

function UseState (props) {const [count, setCount] = useState(() => {return props.count || 0;});

  function handleSetCount () {
    // 传递回调函数设置状态,setCount 是异步的
    setCount(count => {
      return count + 1;

      // 这样设置 title 就会变成同步的
      // const newCount = count + 1;
      // document.title = newCount; // 1
      // return newCount;
    });
    document.title = count; // 0
  }

  return <div>
    <span>{count}</span>
    <button onClick={handleSetCount}>plus</button>
  </div>
}

export default UseState;

2.useReducer

useReducer 是另一种让函数组件保留状态的形式。

import React, {useReducer} from 'react';

function UseReducer () {function reducer(state, action) {switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }

  const [count, dispatch] = useReducer(reducer, 0);

  return <div>
    <p>useReducer</p>

    <button onClick={() => dispatch({type: 'increment'})}>+</button>
    <span>{count}</span>
    <button onClick={() => dispatch({type: 'decrement'})}>-</button>
  </div>
}

export default UseReducer;

相较于 useState 的劣势在于,如果子组件要批改父组件中的数据时,能够间接将父组件中的 dispatch 办法传递给子组件,
而后依据不同的类型,做不同的解决。

3.useContext

在跨组件层级获取数据时简化获取数据的代码。

  • 跨层级获取数据

    import React, {createContext} from 'react';
    
    const countContext = createContext();
    
    function UseContext () {
    return <div>
      <header>UseContext</header>
      <countContext.Provider value={100}>
        <Foo/>
      </countContext.Provider>
    </div>
    }
    
    function Foo () {
    return <countContext.Consumer>
      {
        value => {return <div>{value}</div>
        }
      }
    </countContext.Consumer>
    }
    
    export default UseContext;
  • 简化代码

    import React, {createContext, useContext} from 'react';
    
    const countContext = createContext();
    
    function UseContext () {
    return <div>
      <header>UseContext</header>
      <countContext.Provider value={100}>
        <Foo/>
      </countContext.Provider>
    </div>
    }
    
    function Foo () {const value = useContext(countContext);
    return <div>{value}</div>
    }
    
    export default UseContext;

4.useEffect

让函数组件领有解决副作用的能力,相似生命周期函数。

1. useEffect 执行机会

  1. useEffect(() => {}): componentDidMount、componentDidUpdate
  2. useEffect(() => {}, []): componentDidMount
  3. useEffect(() => () => {}): componentWillUnMount

能够把 useEffect 看做 componentDidMount、componentDidUpdate、componentWillUnMount 这三个生命周期函数的组合。

import React, {useEffect, useState} from "react";

function UseEffect() {const [count, setCount] = useState(0);

  // 组件挂载和组件更新时执行
  // useEffect(() => {//   console.log('run')
  // });

  // 组件挂载时执行
  // useEffect(() => {//   console.log('run')
  // }, []);

  // 革除上一个 effect
  useEffect(() => {console.log('1')
    return () => {console.log('run');
    }
  });

  return <div>
    <header>useEffect</header>
    <span>{count}</span>
    <button onClick={() => setCount(count + 1)}>plus</button>
  </div>
}

export default UseEffect;

2. useEffect 应用形式

  1. 为 window 对象增加滚动事件
  2. 设置定时器让 count 数值每隔一秒减少 1
import React, {useEffect, useState} from "react";
import {root} from "../index";

function UseEffect() {function onScroll() {console.log('页面滚动了');
  }

  // 挂载之后绑定事件
  useEffect(() => {window.addEventListener('scroll', onScroll)
    return () => {console.log('run')
      window.removeEventListener('scroll', onScroll);
    }
  }, []);


  const [count, setCount] = useState(0);

  useEffect(() => {const timer = setInterval(() => {setCount(count => count + 1);
    }, 1000)
    return () => {clearInterval(timer);
    }
  }, [])

  useEffect(() => () => {console.log('卸载组件');
  })

  return (
    <div>
      <header>useEffect</header>
      <div>{count}</div>
      <button onClick={() => root.unmount()}> 卸载组件 </button>
    </div>
  )
}

export default UseEffect;

3. useEffect 解决的问题

  1. 依照用处将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
  2. 简化反复代码,是组件外部代码更加清晰(在类组件中通常 componentDidMount 和 componentDidUpdate 中的代码是一样的)

4. useEffect 数据监测

只有指定数据发生变化的时候才会去触发 effect

const [count, setCount] = useState(0);
const [num, setNum] = useState(0);

// 只有 count 扭转的时候才会执行
useEffect(() => {document.title = count;}, [count]);

return (
  <div>
    <header>useEffect</header>
    <span>{count} {num}</span>
    <button onClick={() => setCount(count => count + 1)}>addCount</button>
    <button onClick={() => setNum(count => count + 1)}>addNum</button>

  </div>
)

5.useEffect 联合异步函数

useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise

useEffect(() => {
  // 编写一个立刻执行函数
  (async () => {await axios.get()
  })();})

6.useMemo

useMemo 的行为相似 Vue 中的计算属性,能够监测某一个值的变动,依据变动值计算新值。
useMemo 会缓存计算结果,如果监测值没有发生变化,即便组件从新渲染也不会从新计算,此行为能够有助于防止在每个渲染上进行低廉的计算。

import {useMemo} from 'react';

// 挂载实现后默认执行一次,如果监听的 count 产生了变动则再次执行
const result = useMemo(() => {return result;}, [count]);

7. 应用 memo 办法进步组件性能

性能优化,如果本组件中的数据没有产生任何变动,就阻止组件进行更新。相似类组件中的 PureComponent 和 shouldComponentUpdate

import React, {memo, useState} from "react";

function Memo () {const [count, setCount] = useState(0);

  return <div>
    <span>{count}</span>
    <button onClick={() => setCount(count => count + 1)}>+1</button>
    <Foo/>
  </div>
}

// 每当 count + 1 Foo 即便没有扭转也会被从新渲染
// function Foo() {//   console.log('从新渲染');
//   return <div>Foo 组件 </div>
// }

// 这样 Memo 组件更新就不会连带 Foo 更新
const Foo = memo(() => {console.log('从新渲染');
  return <div>Foo 组件 </div>
})

// 当 Memo 没有产生数据变动且父组件产生了变动,Memo 组件不会被更新
export default memo(Memo);

8.useCallback

性能优化,缓存函数,使组件从新渲染时失去雷同的函数实例。

Foo 子组件被从新渲染

import React, {useCallback, useState, memo} from 'react';

function UseCallback() {const [count, setCount] = useState(0);

  function resetCount () {setCount(0);
  }

  return <div>
    <span>{count}</span>
    <button onClick={() => setCount(count => count + 1)}>+1</button>
    <Foo resetCount={resetCount}/>
  </div>
}

const Foo = memo((props) => {
  /**
   * 当 count 产生扭转时会更新父组件,父组件中的 resetCount 函数被从新定义
   * 这时的 resetCount 与 Foo props 中的 resetCount 实例曾经产生了扭转,所以 Foo 会被从新渲染
   */
  console.log('从新渲染');
  return <div>Foo 组件
    <button onClick={props.resetCount}>resetCount</button>
  </div>
})

export default UseCallback;

// 应用 useCallback 优化 resetCount

const resetCount = useCallback(() => setCount(0), [setCount]);

9.useRef

1. 获取 DOM 对象

import React, {useRef} from 'react';

function UseRef () {const userName = useRef();

  const handler = () => console.log(userName.current);

  return <div>
    <header>UseRef</header>
    <input ref={userName} onChange={handler}/>
  </div>
}

export default UseRef;

2.useRef 保留数据

即便组件从新渲染,保留的数据依然还在,保留的数据被更改不会触发组件渲染(跨组件周期)。

// 保留数据
  const [count, setCount] = useState(0);

  // 即便组件更新并不会重置,所以 stopCount 能够拿到 timer
  let timer = useRef();

  useEffect(() => {timer.current = setInterval(() => {setCount(count => count + 1);
    }, 1000);
  }, [])

  const stopCount = () => {clearInterval(timer.current);
  }

  return <div>
    <header>useRef</header>
    <span>{count}</span>
    <button onClick={stopCount}>stop</button>
  </div>

10. 自定义 hook

  • 自定义 hook 是规范的封装和共享逻辑的形式
  • 自定义 hook 是一个函数,其名称以 use 结尾
  • 自定义 hook 其实就是逻辑和内置 hook 的组合
import React, {useEffect, useState} from 'react';
import axios from 'axios';

/**
 * 以后组件须要再组件挂载实现之后,获取文章数据
 * 假如获取文章数据的申请是共享逻辑,尝试把它写到自定义 hook 当中
 */

// 创立自定义 hook
function useGetEssay () {const [essay, setEssay] = useState({});

  useEffect(() => {axios.get('https://www.fastmock.site/mock/eefbb8ce7302645510629510865adb64/api/essay')
      .then(res => setEssay(res.data));
  }, []);

  return [essay, setEssay];
}

function CustomizationHook () {const [essay, setEssay] = useGetEssay(null);

  return <div>
    <header>CustomizationHook</header>
    <div>
      <p>{essay.title}</p>
      <div>{essay.content}</div>
    </div>
  </div>
}

export default CustomizationHook;
  1. 提取表单中的罕用逻辑

    
    function useUpdateInput(initialValue) {const [value, setValue] = useState(initialValue);
      return {
     value,
     onChange: e => setValue(value => value = e.target.value)
      }
    }
    
    function CustomizationHook() {const userNameInput = useUpdateInput('');
     const passwordInput = useUpdateInput('');
    
     const submitForm = e => {e.preventDefault();
    console.log(userNameInput.value, passwordInput.value)
     }
    
      return <form onSubmit={submitForm}>
     {/* 为了绑定数据,每个表单元素都须要设置 value 和 onChange,这就属于共享逻辑 */}
     <input type="text" name="username" {...userNameInput} />
     <input type="text" name="password" {...passwordInput} />
     <input type="submit"/>
      </form>
    }
退出移动版