进来了解一下 React hooks

481 阅读6分钟

React hooks 的入门笔记

写在前面,笔者才疏学浅,写此文章仅做参考!不对之处,敬请海涵!

React v16.8+ 版本增加了 hooks,笔者认为这极大的改变了 React 的开发模式,并且这是有利的。本文主要记录useStateuseEffectuseReduceruseContext等常用的钩子函数。

比较代码

  • 不使用 Hooks
class HookTest extends Component {
  constructor(props) {
    super(props);
    this.state = { count:0 }
    this.addCount = this.addCount.bind(this)
  }
  render() {
    return ( <div>{this.state.count}<button onClick={this.addCount}>点我加1</button></div> );
  }
  addCount(){
    this.setState({ count: ++this.state.count });
  }
}
  • 使用 Hooks
function HookTest() {
  const [count, setCount] = useState(0);
  render() {
        return ( 
        <div>
            {count}<button onClick={()=>{setCount(count+1)}}>点我加1</button>
        </div>    
        );
  }
}

代码简洁程度不言而喻,作为一个初学者,开始就学到精简的写法,还是很不错的!

React 组件化

一切皆为组件、万物都是函数

  • 功能(无状态)组件
    • Function Component 功能组件也叫做无状态组件,一般只负责渲染
    function Hello (){
         return(
            <div>
                <h1>Function Component</h1>
            </div>
        )
    }
    
  • 渲染组件
    • Presentational Component 和功能(无状态)组件类似 全凭 参数 props
    const hello =(props)=>{
        return(
            <div>
                <h1>Presentational Component</h1>
            </div>
        )
    }
    
  • 类(有状态)组件
    • Class Component 类组件就是状态组件,一般有交互逻辑也业务逻辑
    class Hello extend Component{
         ...(业务逻辑)
         return(
            <div>
                <h1>Class Component</h1>
            </div>
        )
    }
    

写组件的时候,应该考虑是否可以作为无状态组件、是否分离UI组件等。这样有利于后期代码的维护。

Hook 函数必须以 "use" 命名开头,因为这样才方便 eslint 做检查,防止用 condition 判断包裹 useHook 语句。

useState

🐂🍺 可以不用 this

使用

import { useState } from 'react';

fucntion HookTest(){
    const [count, setCount] = useState(0);
    // 上面是 ES6 解构赋值 定义变量名的同时 设置修改方法 并初始化
    // 等同于 
    // let _useState = useState(0);
    // let count = _useState[0];
    // let setCount = _useState[1];
    // 上面的意思:`useState`这个函数接收的参数是状态的初始值(Initial state),
    // 它返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。 
    // 所以上面的代码的意思就是声明了一个状态变量为count,并把它的初始值设为0,
    // 同时提供了一个可以改变`count`的状态值的方法函数。
    
    // 在 jsx 中使用
    return <h1>{count}<button onClick={()=>setCount(count +1)}></button></h1>
}

重点知识

  • useState的初始值,只在第一次有效
    const Child = ({ data }) => {
      const [name, setName] = useState(data); // 只会在首次渲染组件时执行
      return (
        <div>
          <div>child</div>
          <div>
            {name} --- {data}
           // 首次 rose --- rose
           // 点击按钮后 rose --- jack
          </div>
        </div>
      );
    };
    
    const Hook = () => {
      const [name, setName] = useState('rose');
      return (
        <div>
          <button onClick={() => setName('jack')}>update name </button>
          <Child data={name} />
        </div>
      );
    };
    
    export default Hook;
    
  • 不可以在 if 中声明
    • React Hooks 并不是通过 Proxy 或者 getters 实现的,而是通过数组实现的,每次 useState 都会改变下标,如果 useState被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。

手写 useState

let state = []; // state数组用来保存数据 
let index = 0; // index用来对应每一个数组项 
function useState(initialState) { 
    let currentIndex = index; // currentIndex用来保存当前index 
    state[currentIndex] = state[currentIndex] || initialState; 
    function setState(newState) { 
        state[currentIndex] = newState; 
    render(); 
    } 
index += 1; // 每次修改完成之后index加1 
return [state[currentIndex], setState]; 
} 
function render() { 
    index = 0; // render时需要重新恢复index ReactDom.render(<Counter />, document.getElementById("root"));
}

实现思路如下:

  • state声明成数组,每一个数据对应数组的某一项
  • 声明一个索引index,每个数据对应一个索引值
  • setState通过操作索引去设置值
  • 每调用一次useState需要将 index+=1。这样的话确保多个数据具有不同的索引值
  • 返回的值也是通过索引获取
  • 每次 render 重新渲染时需要将索引 index 置为 0,确保每个数据对应的索引每次都是一致的(render 渲染组件重新渲染,组件内所有的 useState 会执行一次,每个数据又会分配一个索引,因此每次需要将 index 置为 0,确保每次的索引一致。这也是为什么 hooks 不能写在 if,while 等条件判断中)。

上面最核心的一点就是确保每个 useState 的数据对应的 index 必须一致。 也就是说:

  • 第一次渲染时,count 对应的索引值为 0,num 对应的索引值为 1。
  • 第二次渲染时,count 对应的索引值仍然为 0,num 对应的索引值为 1。
  • ...

useEffect

🐂🍺 在函数组件里面使用 class 组件的生命周期函数,并且还是生命周期函数的集合!

class 组件生命周期

// 挂载阶段(常用到的钩子函数)
componentWillmount()
render()
componentDidMount()

//更新阶段
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate ()
render()
componentDidUpdate()

// 卸载阶段
componentWillUnmount()

使用 useEffect 替代

// React 首次渲染和之后的每次渲染都会调用一遍 useEffect 函数,
// 而之前我们要用两个生命周期函数
// 分别表示首次渲染( componentDidMount )和更新导致的重新渲染( componentDidUpdate )。
// useEffect 中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而 
// componentDidMount和 componentDidUpdate 中的代码都是同步执行的。

useEffect(() => {
    console.log(123);
});

知识点

  • 可以写多个useEffect
  • 第二个参数是一个[],数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,我们才进行解绑。但是当传空数组[]时,就是当组件将被销毁时才进行解绑,这也就实现了 componentWillUnmount 的生命周期函数
    • 如果我们想每次 count 发生变化,我们都进行解绑,只需要在第二个参数的数组里加入 count 变量就可以了。
    useEffect(() => {
     console.log('++++');
    }, [count]);
    
  • useEffectreturn里面可以做取消订阅的事
    • componentWillUnmount 生命周期函数(组件将要被卸载时执行)。比如我们的定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错。componentWillUnmount -解绑副作用
    useEffect(() => { 
        const subscription = 订阅事件!
        return () => { 解绑事件! } }
    ,[])
    

useReducer

🐂🍺 它可以增强我们的Reducer,实现类似Redux的功能。 了解更多 Redux 的知识可以移步 你想知道的Redux和React-Router都在这里

使用

import React, { useReducer } from 'react';

function ReducerDemo(){
   const [ count , dispatch ] =useReducer((state,action)=>{
       switch(action){
           case 'add':
               return state+1
           case 'sub':
               return state-1
           default:
               return state
       }
   },0)
   return (
      <div>
          <h1>{count}</h1>
          <button onClick={()=>dispatch('add')}>Increment</button>
          <button onClick={()=>dispatch('sub')}>Decrement</button>
      </div>
   )

}

export default ReducerDemo

useContext

🐂🍺 useContext 跨越组件层级直接传递变量,实现共享。

需要注意的是 useContext 和 redux 的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和 useReducer 的配合使用,可以实现类似 Redux 的作用。

import React, {
  useState,
  useEffect,
  createContext,
  useContext,
  useReducer
} from 'react';

// 1、创建一个 createContext
const CountContext = createContext();

function Counter() {
  const count = useContext(CountContext); //一句话就可以得到count
  return <h2>{count}</h2>;
}

function HookTest() {
  const [count, dispatch] = useReducer((state, action) => {
    switch (action) {
      case 'add':
        return state + 1;
      case 'sub':
        return state - 1;
      default:
        return state;
    }
  }, 0);

  return (
    <div>
      {/* 2、 创建一个上下文变量 */}
      <CountContext.Provider value={count}>
        <Counter />
      </CountContext.Provider>
      {/* {count} */}
      <button
        onClick={() => {
          dispatch('add');
        }}
      >
        加1
      </button>{' '}
      <button
        onClick={() => {
          dispatch('sub');
        }}
      >
        减1
      </button>
    </div>
  );
}

export default HookTest;

使用useContextuseReducer是可以实现类似Redux的效果

使用步骤

  • 创建一个 createContext;const CountContext = createContext();
  • 创建一个上下文变量
     <CountContext.Provider value={count}>
        <Counter />
     </CountContext.Provider>
    
  • 在子组件中通过 useContext获得
    function Counter() {
        const count = useContext(CountContext); //一句话就可以得到count
        return <h2>{count}</h2>;
    }
    

总结

路漫漫其修远兮,吾将上下而求索。笔者目前所遇之困境,唯学习、自省、锻炼可解。另外,笔者希望屏幕面前的你能不断学习、每日自省、加强锻炼......