React Hook 详解上

114 阅读4分钟

Hook

是React16.8 的新增特性,它可以让你在不编写 class 的情况下使用state以及其他的React特性

Hook API

useState              //状态
useEffect             //副作用
useContext            //上下文
useReducer            //Redux
useCallback           //回调
useMemo               //记忆
useRef                //引用
useImperativeHandle   //自定义ref 
useLayoutEffect       //布局副作用
useDebugValue         //自定义hook

useState

使用状态
const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name: 'F'})

不可局部更新

setState不会帮我们合并属性,使用...进行合并

import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
  const [user,setUser] = useState({name:'Frank', age: 18})
  const onClick = ()=>{
    setUser({
      ...user,              //合并之前的属性
      name: 'Jack'          //覆盖之前的属性
    })
  }
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

地址要变

setState(obj)如果obj地址不变,那么React就认为数据没有变化,必须是新对象才会更新,

function App() {
  const [user,setUser] = useState({name:'Frank', age: 18})
  const onClick = ()=>{
    user.name = "Jack",     //在旧对象上修改数据,React不会更新
    setUser(user)
  }

useState 可以接受函数

function App() {
  const [user,setUser] = useState( ()=>({neme: "Frank", age: 18})
)
该函数返回初始state,且只执行了一次,减少多余的计算过程

setState接受函数

function App() {
  const [n, setN] = useState(0)
  const onClick = ()=>{
    setN(n+1)
    setN(n+1)       // n 不能加 2,只执行最后一次的操作
    setN(i=>i+1)    //函数写法,并不知道i的值是多少,只知道i+1这个操作
    setN(i=>i+1)    //i是占位符,n会自动替换
  }

useReducer

用来践行Flux/Redux思想,useReducer是useState的复杂版

创建useReducer,4步
一:创建初始值initialState
二:创建所有操作 reducer(state, action)
三:传给useReducer,得到读和写API
四:调用 写({type:'操作类型'})

useReducer示例代码

import React, {useState, useReducer} from "react";
import ReactDOM from "react-deom";
const initial = { n: 0 }
//把对n的操作写到函数里,第1个参数是旧的状态,2是操作类型,最后返回一个新类型
const reducer = (state, action) =>{
  if(action.type === 'add'){ 
    return {n: state.n + action.number}
  }else if(action.type === 'mulit'){
    return {n:  state.n*2}
  }else{
    throw new Error('unknown type')
  }
}
function App(){
//得到读,写API
  const {state, dispatch} = useReducer(reducer, initial)
  const {n} = state
  const onClick = () =>{
//写是调用reducer里的add操作
    dispatch({type:'add', number: 1})
  };
  const onClick2 = () =>{
    display({type:'add', number: 2})
  }
  return (
    <div className="App">
      <h1>n: {n}</h1>
      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  )
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

使用 useReducer 代替Redux

一:将数据集中在一个store对象
二:将所有操作集中在reducer
三:创建一个Context
四:创建对数据读写API
五:将第四步的内容放在第三步的Context
六:用Context.Provider将Context提供给所有组件
七:各个组件用useContext获取读写API 

示例代码

import React, { useReducer, useContext, useEffect } from "react";
import ReactDOM from "react-dom";

//初始化数据,store统一储存仓库
const store = {
  user: null,
  books: null,
  movies: null
};
function reducer = (state, action) => {
  switch (action.type) {
    case "setUser":
      return { ...state, user: action.user };
    case "setBooks":
      return { ...state, books: action.books };
    case "setMovies":
      return { ...state, movies: action.movies };
    default:
      throw new Error();
  }
}
const Context = React.createContext(null);
function App() {
  const [state, dispatch] = useReducer(reducer, store);
  const api = { state, dispatch };
  return (
    <Context.Provider value={api}>
      <User />
      <hr />
      <Books />
      <Movies />
    </Context.Provider>
  );
}

function User() {
  const { state, dispatch } = useContext(Context);
//模拟AJAX,获取用户信息
  useEffect(() => {
    ajax("/user").then(user => {
      dispatch({ type: "setUser", user: user });
    });
  }, []);
  return (
    <div>
      <h1>个人信息</h1>
      <div>name: {state.user ? state.user.name : ""}</div>
    </div>
  );
}

function Books() {
  const { state, dispatch } = useContext(Context);
  useEffect(() => {
    ajax("/books").then(books => {
      dispatch({ type: "setBooks", books: books });
    });
  }, []);
  return (
    <div>
      <h1>我的书籍</h1>
      <ol>
        {state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
      </ol>
    </div>
  );
}

function Movies() {
  const { state, dispatch } = useContext(Context);
  useEffect(() => {
    ajax("/movies").then(movies => {
      dispatch({ type: "setMovies", movies: movies });
    });
  }, []);
  return (
    <div>
      <h1>我的电影</h1>
      <ol>
        {state.movies
          ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>)
          : "加载中"}
      </ol>
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useContext

上下文:全局变量是全局的上下文,上下文是局部的全局变量

使用方法
一:使用C = createContext(initial) 创建上下文
二:使用<C.provider>圈定作用域
三:在作用域内使用 useContext(C) 来使用上下文

useContext不是响应式的,你在一个模块将C里面的值改变,另一个模块不会感知到这个变化

import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";

//创建Context,初始值空,C就是可以使用的全局变量
const C = createContext(null)
function App(){
  const [n, setN] = useState(0);
  return (
//圈定作用域,给一个初始值(读和写接口),作用域里面的所有组件都可以使用
    <C.Provider value={{n, setN}}>
      <div className="App">
        <Baba />
      </div>
    </C.Provider>
  );
}

function Baba(){
  return (
    <div>
      我是爸爸 <Child />
    </div>
  );
}

function Child(){
//使用useContext(C);得到读写接口
  const {n, setN} = useContext(C);
  const onClick = () =>{
    setN(i => i+1);
  };
  return (
    <div>
      我是儿子 我得到的 n:{n}
      <button onClick={onClick}>+1</button>
    </div>
  )
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

详细资料点击:React内置Hook API