React学习笔记——Hooks

115 阅读11分钟

Hooks

Hooks 是 React 中的核心

函数式组件中存在的问题:

  1. 必须是纯函数
  1. 不能包含状态(state)
  1. 不支持生命周期(没有继承,无法继承自 React.Component)

纯函数:一个函数的返回结果,只依赖于自己的参数,并且执行的过程中没有副作用

副作用: 除了干自己的主要任务以外,还做了其他的事情。当一个函数调用的时候,除了返回这个函数值以外,还对主调用函数产生了附加的影响

// 纯函数
let x = 1;

function add(a, b) {
  return a + b;
}
// 不是纯函数,x 是外部变量,依赖外部变量,而不是自己的参数
console.log(add(x, 10));

const obj = {};

function fun(b) {
  obj.a = 10;
  return b;
}
// 不是纯函数,改变了全局作用域的 obj,产生了副作用
console.log(fun(5), obj);

// 纯函数
let a = 1;
let b = 2;
/* 
 1. 他没有改变外部变量的值
 2. 每次调用的时候,传入的值相同,每次返回的结果也相同
 相同的输入得到相同的输出
*/
function sum(x, y) {
  return x + y;
}

console.log(sum(a, b));

函数式编程不是不需要副作用,只是在需要的时候限制副作用

类组件存在的问题:

1.代码很重

2.每次创建的组件都要继承 React.Component 实例

函数组件存在问题:

1.纯函数没有状态

2.纯函数没有生命周期

3.纯函数没有 this

4.只能是纯函数

函数式组件只能用作 UI 展示,涉及到状态的管理只能使用 类组件

因此就是想用纯函数组件,并且可以进行对应的状态管理,因此 react 设计团队,设计了 hooks

组件尽量写成纯函数,如果需要外部 的数据或者是功能,准备一把钩子,把涉及到的数据和功能的代码勾进纯函数组件里面

useState

useState 定义

声明状态变量:

import React, { useState } from 'react';

export default function App() {
  const arr = useState(0);
  console.log(arr[0], arr[1]);
  /* 打印结果 0 ƒ dispatchSetState(fiber, queue, action) {
    {
      if (typeof arguments[3] === 'function') {
        error("State updates from the useState() and useReducer() Hooks don't support the " + 'sec…
 */
  /* 
     useState(0)
     useState会返回一个数组
     第一元素是当前的状态
     第二个元素是 一个函数,这个函数的作用就是设置修改状态
  
  */

  const state = arr[0];
  const setState = arr[1];

  return (
    <div>
      <h2>{state}</h2>

      <button onClick={() => setState(state + 1)}>+1</button>

    </div>

  );
}

useState 基本使用

import React, { useState } from 'react';

export default function App() {
  // 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
  const [state, setState] = useState(0);
  return (
    <div>
      <h1>{state}</h1>

      <button onClick={() => setState(state + 1)}>+1</button>

    </div>

  );
}

函数式更新:

import React, { useState } from 'react';

export default function App() {
  // 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h1>{count}</h1>

      <button onClick={increment}>+1</button>

    </div>

  );
}

多个 useState

import React, { useState } from 'react';

export default function App() {
  // 声明一个 state 的变量,赋值初始化,只在首次渲染的时候使用,如果去更新的话这个会被忽略
  const [money, setMoney] = useState(10000);
  const [stars, setStars] = useState(['迪丽热巴', '杨幂', '沈梦瑶']);
  const [people, setPeople] = useState({ name: '张三', age: 18 });
  return (
    <div>
      <h1>{money}</h1>

      <button onClick={() => setMoney(money + 1)}>加钱</button>

      <ul>
        {stars.map((item) => (
          <li key={item}>{item}</li>

        ))}
        {/* 
          setStars 去更新的时候,注意新旧之不能相等
          像数组,对象引用数据类型需要拷贝为新对象才能触发更新
        */}
        <button onClick={() => setStars([...stars, '袁一琦'])}>加人</button>

      </ul>

      <h2>{people.name}</h2>

      <button onClick={() => setPeople({ ...people, name: '李四' })}>
        修改名字
      </button>

    </div>

  );
}
import React, { useState } from 'react';

export default function App() {
  const [staff, setStaff] = useState([
    { id: 1, name: '张三', salary: 180000 },
    { id: 2, name: '李四', salary: 180000 },
    { id: 3, name: '王五', salary: 180000 },
  ]);
  const change = (index) => {
    // 浅拷贝
    let newStaff = [...staff];
    newStaff[index].salary += 10000;
    setStaff(newStaff);
  };
  return (
    <div>
      <ul>
        {staff.map((item, index) => {
          return (
            <li key={item.id}>
              <span>员工姓名:{item.name}</span>

              <span>员工薪水:{item.salary}</span>

              <button
                onClick={() => {
                  change(index);
                }}
              >
                加工资
              </button>

            </li>

          );
        })}
      </ul>

    </div>

  );
}
import React, { useState } from 'react';

const Son = (props) => {
  let [state, setState] = useState(() => {
    if (props.init > 60) {
      console.log('大于60');
      return 60 * 60;
    }
  });
  return (
    <div>
      <h1>son组件</h1>

      <p>{state}</p>

      <button
        onClick={() => {
          setState(state + 10);
        }}
      >
        增加10
      </button>

    </div>

  );
};
export default function App() {
  return (
    <div>
      <Son init={70} />
    </div>

  );
}

useState 注意点:

  1. 初始参数值还在组件的首次渲染的时候使用,再次更新时会被忽略
  1. 每次通过 setXXX 修改状态都会引起组件重新渲染
  1. 可以调用多次,每次都是独立的
  1. useState 只可以在函数组件之内使用
  1. 如果初始值需要计算才能得到,可以使用回调函数的形式来确定初始值

useEffect

useEffect:可以为 React 组件完成副作用的操作

一个纯函数组件主要的功能是通过数据渲染的 UI,剩余的其他的操作都是副作用

1.ajax 请求

2.手动 DOM

3.本地存储

useEffect 的第二个参数,是依赖变量,是一个数组,可以有多个依赖项,一旦这一个依赖项发生了汴东,这个 useEffect 就会重新执行

// 组件对应的某个时段做什么操作 => 生命周期函数

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

const Children = () => {
  useEffect(() => {
    return () => {
      console.log('组件被销毁了');
    };
  }, []);
  return (
    <div>
      <h2>children组件</h2>

    </div>

  );
};

export default function App() {
  const [count, setCount] = useState(0);
  const [money, setMoney] = useState(100);
  const [flag, setFlag] = useState(false);

  // 副作用操作 DOM
  useEffect(() => {
    console.log('componentDidMount执行了');
    // 依赖为空的时候只在页面初始的时候只执行一次
  }, []);

  useEffect(() => {
    console.log('componentDidUpdate执行了');
    //第二个参数是依赖数组 只有count变化时才执行,money 变化时不执行
  }, [count]);

  useEffect(() => {
    console.log('componentDidMount执行了');
    // 没有依赖的时候每次都会执行
  });

  return (
    <div>
      <h1>{count}</h1>

      <h2>{money}</h2>

      <button onClick={() => setCount(count + 1)}>增加+1</button>

      <button onClick={() => setMoney(money + 100000)}>加钱</button>

      {flag && <Children />}
      <button onClick={() => setFlag(!flag)}>切换</button>

    </div>

  );
}
import React, { useEffect, useState } from 'react';
import axios from 'axios';

export default function App() {
  let [list, setList] = useState([]);
  // 发起请求
  useEffect(() => {
    const initData = async () => {
      const { data } = await axios.get(
        'https://www.fastmock.site/mock/2728fdedd7e9063e308598df4c68fe46/_api/api/list'
      );
      console.log(data);
      setList(data.data);
    };
    initData();
  }, []);
  return (
    <div>
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>

        ))}
      </ul>

    </div>

  );
}

useEffect 的清除作用:

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

/*  子组件  */
function Child() {
  const [count, setCount] = useState(0);
  /* 
    开启和清除定时器
  */
  useEffect(() => {
    const timer = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
    // 清楚定时器 return 是 Effect 的一个可选的清除机制,每一个 uesEffect 都有一个返回一个清除函数
    // return 执行机制,是在组件被销毁之前才会执行
    // 做清除副作用的操作
    return () => {
      clearInterval(timer);
      console.log('定时器消除了—— 组件销毁了!');
    };
  }, []);
  return (
    <div>
      <h2>{count}</h2>

      <h1>Child</h1>

    </div>

  );
}
export default function App() {
  const [display, setDisplay] = useState(true);
  return (
    <div>
      {display && <Child />}
      <button onClick={() => setDisplay(!display)}>
        {display ? '隐藏child' : '显示child组件'}
      </button>

    </div>

  );
}

依赖项执行时机:

1.默认状态:首次执行,组件渲染初次的时候执行+组件更新执行 useEffect( ) 函数

2.如果依赖项为[],那么只执行一次,在组建首次渲染的时候执行,只执行一次

3.添加特定的依赖项的时候,首次执行 + 依赖项发生更新的时候执行

useContext

定义:帮助跨组件直接传递,实现跨级组件通信

使用的方法:

  1. 使用 createContext 创建一个 Context 对象
  1. 在顶层组件 通过 Provider 提供数据
  1. 底层组件通过 useContext 函数去获取数据
import React, { useState, useContext, createContext } from 'react';

// 创建一个 context

const context = createContext();
// 孙子组件
function GrandSon() {
  const { count, setCount, num, setNum } = useContext(context);
  return (
    <div>
      <h1>GrandSon 组件</h1>

      <div>{count}</div>

      <div>{num}</div>

      <button onClick={() => setCount(count + 1)}>count + 1</button>

      <button onClick={() => setNum(num + 1)}>num + 1</button>

    </div>

  );
}

// 孙子组件
function Son() {
  return (
    <div>
      <GrandSon />
    </div>

  );
}

function Father() {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(10);
  return (
    <div>
      <context.Provider value={{ count, setCount, num, setNum }}>
        <Son />
      </context.Provider>

    </div>

  );
}

export default function App() {
  return (
    <div>
      <Father />
    </div>

  );
}

useRef

主要功能是 获取 DOM 元素或者组件,还可以保存值

使用 useRef 获取 DOM 元素

  1. 引入 useRef,调用 useRef( )
  1. 通过 ref 属性将 useRef 的返回值绑定到 元素身上
  1. useRef( ) 返回的是一个对象,对象内有一个 current 属性
import React, { useRef, useEffect } from 'react';

class Header extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '张三',
    };
  }
  render() {
    return (
      <div>
        <h2>Header 组件</h2>

      </div>

    );
  }
  say = () => {
    console.log('我是 Header 组件方法 say');
  };
}
export default function App() {
  const inputRefs = useRef();
  const useRefs = useRef();
  const headerRefs = useRef();
  // 副作用获取DOM,一进入页面获取焦点
  useEffect(() => {
    inputRefs.current.focus();
  });
  const changeText = () => {
    console.log(headerRefs);
    console.log(headerRefs.current.state.name);
    headerRefs.current.say(); //我是 Header 组件方法 say
    // 获取修改 h2 标签的文本内容
    useRefs.current.innerHTML = inputRefs.current.value;
  };
  return (
    <div>
      <Header ref={headerRefs} />
      <h2 ref={useRefs}>H2标签</h2>

      <input type='text' ref={inputRefs} />
      <button onClick={() => changeText()}>按钮</button>

    </div>

  );
}

useRef

主要功能是 获取 DOM 元素或者组件,还可以保存值

使用 useRef 获取 DOM 元素

  1. 引入 useRef,调用 useRef( )
  1. 通过 ref 属性将 useRef 的返回值绑定到 元素身上
  1. useRef( ) 返回的是一个对象,对象内有一个 current 属性
import React, { useRef, useEffect } from 'react';

class Header extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '张三',
    };
  }
  render() {
    return (
      <div>
        <h2>Header 组件</h2>

      </div>

    );
  }
  say = () => {
    console.log('我是 Header 组件方法 say');
  };
}
export default function App() {
  const inputRefs = useRef();
  const useRefs = useRef();
  const headerRefs = useRef();
  // 副作用获取DOM,一进入页面获取焦点
  useEffect(() => {
    inputRefs.current.focus();
  });
  const changeText = () => {
    console.log(headerRefs);
    console.log(headerRefs.current.state.name);
    headerRefs.current.say(); //我是 Header 组件方法 say
    // 获取修改 h2 标签的文本内容
    useRefs.current.innerHTML = inputRefs.current.value;
  };
  return (
    <div>
      <Header ref={headerRefs} />
      <h2 ref={useRefs}>H2标签</h2>

      <input type='text' ref={inputRefs} />
      <button onClick={() => changeText()}>按钮</button>

    </div>

  );
}

使用 useRef 保存值

// 使用 useRef 保存值
import React, { useRef, useState } from 'react';

export default function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  return (
    <div>
      <h1>count:{count}</h1>

      <h1>countRef:{countRef.current}</h1>

      {/* count 会通过点击 +1 但是 useRef 中的值不会发生变化,所以可以用来保存数据  */}
      <button onClick={() => setCount(count + 1)}>+1</button>

    </div>

  );
}

useReducer

useReducer 是一个高级 Hook,(useState,useEffect,useRef 是必须的 hook)

useState 提供组件的状态,useReducer 是 useState 升级版,所有的 useState 规则,useReducer 都适用

基本使用:

import React, { useReducer } from 'react';

// 初始化状态
const initState = 10;

/* reducer 函数 */
/* 
  1. reducer 函数是 state 的处理函数
  2. state:用于获取state状态的值
  3. action:用于修改 state 的派发器
  一种用于分配事件处理的机制
  通过action 发送操作 state 的指令
  具体的修改行为由 reducer 执行
  接收当前的 state 和触发的动作的 action ,计算并返回最新的 state

*/
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      throw new Error('unknow action');
  }
}
export default function App() {
  // useReducer 接受两个参数,第一个是 reducer 函数 第二个参数,初始值
  // const arr = useReducer(reducer, initState); // 0 function
  //  dispatch 接受一个参数,执行对应的 action,dispatch 执行以后,对应的 state 会改变,组件会重新渲染,显示最新的 state
  const [state, dispatch] = useReducer(reducer, initState); // 0 function

  // console.log(arr); // [0, ƒ]

  return (
    <div>
      <h1>initState:{state}</h1>

      {/* dispatch 用来接收一个 action 函数,reducer 函数中的 action ,用来触发reducer 函数,用来触发 reducer 函数,更新最新的状态*/}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>

      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>

    </div>

  );
}

useReducer 的使用步骤:

  1. 定义一个 initState
  1. 定义一个 reducer 函数,把所有的操作方法都放在这个函数里面
  1. 把 initState 跟 reducer,通过 useReducer 关联起来,返回一个当前的 state 和 dispatch
  1. 当需要计算的时候,使用 dispatch 传递一个 action 值,触发的 reducer 函数,返回一个新的 state
  1. useReducer 数据不共享,共享的事 reducer 函数,redux
import React, { useReducer } from 'react';

const initState = {
  name: '',
  age: '',
  hobby: '',
};

function reducer(state, action) {
  switch (action.type) {
    case 'change':
      // 传入 inputvlue 中的值赋给 initState
      return { ...state, ...action.inputValue };
    default:
      break;
  }
}
export default function App() {
  const [state, dispatch] = useReducer(reducer, initState);
  const click = () => {
    console.log(
      `姓名:${state.name},年龄:${state.age},爱好:${state.hobby}`
    );
  };
  return (
    <div>
      <form>
        <p>
          姓名:
          <input
            onChange={(e) => {
              dispatch({
                type: 'change',
                inputValue: { name: e.target.value },
              });
            }}
          ></input>

        </p>

        <p>
          年龄:
          <input
            onChange={(e) => {
              dispatch({
                type: 'change',
                inputValue: { age: e.target.value },
              });
            }}
          ></input>

        </p>

        <p>
          爱好:
          <input
            onChange={(e) => {
              dispatch({
                type: 'change',
                inputValue: { hobby: e.target.value },
              });
            }}
          ></input>

        </p>

        <button onClick={click} type='button'>
          确认
        </button>

      </form>

    </div>

  );
}
import React, { useReducer } from 'react';

// 初始化状态
const initState = 0;

/* reducer 函数 */
/* 
  1. reducer 函数是 state 的处理函数
  2. state:用于获取state状态的值
  3. action:用于修改 state 的派发器
  一种用于分配事件处理的机制
  通过action 发送操作 state 的指令
  具体的修改行为由 reducer 执行
  接收当前的 state 和触发的动作的 action ,计算并返回最新的 state

*/
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      throw new Error('unknow action');
  }
}
// 父组件
function Father() {
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <div>
      <h1>父组件{state}</h1>

      <button onClick={() => dispatch({ type: 'increment' })}>+</button>

    </div>

  );
}

// 子组件
function Son() {
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <div>
      <h1>子组件{state}</h1>

      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>

    </div>

  );
}
export default function App() {
  return (
    <div>
      <Father />
      <Son />
    </div>

  );
}

useCallback

解决了重复渲染的问题,性能优化相关:

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

// function Son() {
//   console.log('渲染了');
//   return (
//     <div>
//       <h2>Son 组件</h2>

//     </div>

//   );
// }

// memo 可以解决组件的重复渲染
// 高阶组件 将组件作为一个参数并且返回一个新的组件
const Son = memo(function (props) {
  console.log('Son渲染了'); //传递 function 过来的时候 依然重复渲染了
  return (
    <div>
      <h2>Son 组件</h2>

      <h2>{props.name}</h2>

    </div>

  );
});
export default function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('张三');
  const change = (name) => {
    setName(name);
  };
  return (
    <div>
      <h1>{count}</h1>

      {/* 每次调用 setCount 都会重新渲染父组件,从而渲染子组件 */}
      <button onClick={() => setCount(count + 1)}>Add</button>

      <Son name={name} onClick={change} />
    </div>

  );
}
function App() {
  let n = 0;
  return <div></div>;
}

每次调用的都是 不同的 n,因为每次都会产生新的局部变量,所以每次 change 执行的时候,会产生一个新的 change

useCallback 起到了一个缓存的作用,即便父组件重新渲染了,useCallback 包裹的函数不会重新生成,会返回上一次函数的引用,需要 memo 配合 useCallback 使用

export default function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('张三');
  const change = useCallback((name) => {
    setName(name);
  }, []);
  return (
    <div>
      <h1>{count}</h1>

      {/* 每次调用 setCount 都会重新渲染父组件,从而渲染子组件 */}
      <button onClick={() => setCount(count + 1)}>Add</button>

      <Son name={name} onClick={change} />
    </div>

  );
}

useMemo

与 useCallback 功能一样防止重复渲染

父组件渲染,会生成一个新的对象,导致传递给子组件的 person 属性发生了变化,进而导致子组件会重新渲染,浅比较

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

const Son = memo(function (props) {
  console.log(props);
  console.log('son 组件 render.....');
  return (
    <div>
      <h1>Son组件 </h1>

    </div>

  );
});
export default function App() {
  const [count, setCount] = useState(0);
  const person = {
    name: '张三',
    age: 18,
  };

  /* 
    1. 第一个参数:一个函数,返回的对象指向同一个引用,不会创建新的对象
    2. 第二个参数是一个数组,只有数组的变量改变才会使第一函数返回一个新的对象,与 useEffect 相似
  
  */
  const data = useMemo(() => {
    return { name: '张三', age: 18 };
  }, []);
  return (
    <div>
      {/* <Son person={person} /> */}
      <Son data={data} />
      <button onClick={() => setCount(count + 1)}>{count}</button>

    </div>

  );
}