React hooks 详解

Snipaste_2021-11-29_23-27-35

官方文档


useState

使用状态:
  • const[n, setN] = React.useState(0)
  • const[user, setUser] = React.useState({name:'frank})
注意事项1:不可局部更新

如果 state 是一个对象,能否只更新部分?

答案是不行,示例代码

因为 setState 不会帮我们合并属性。

注意事项2:对象地址要变

setState (obj) 如果 obj 地址不变,即使数据变了,那么 React 认为 数据没有变化。

import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
  const [user,setUser] = useState({name:'Frank', age: 18})
  const onClick = ()=>{
    // 这么修改没有用
    user.name = 'java'
    setUser(user)
    // 正确y
    setUser({
        ...user,
        name: 'java'
    })
  }
  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);

useState 接收函数

const [state, setState] = useState(()=>{return initialState})
该函数返回初始 state, 且只执行一次

setState 接收函数

setN(i => i + 1)

优先使用函数,demo

自定义 useState


useReducer

用来践行 Flux/Redux 的思想

共四步:

  1. 创建初始值 initialState
  2. 创建所有操作 reduce(state, actioin)
  3. 传给 useReducer, 得到读和写 API
  4. 调用写 ({type: ‘操作类型’})

总的来说 useReducer 是 useState 的复杂版。

demo

// 创建初始值
const initial = {
  n: 0
};
// 创建所有操作
const reducer = (state, action) => {
  if (action.type === "add") {
    return { n: state.n + action.number };
  } else if (action.type === "multi") {
    return { n: state.n * 2 };
  } else {
    throw new Error("unknown type");
  }
};
function App() {
   // useReducer
  const [state, dispatch] = useReducer(reducer, initial);
  const { n } = state;
  const onClick = () => {
    // 调用 写 ({type: ‘操作类型’}) 
    dispatch({ type: "add", number: 1 });
  };
  const onClick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  );
}

useReducer 表单例子

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

const initFormData = {
  name: "",
  age: 18,
  nationality: "汉族",
};

const reducer = (state, action) => {
  switch (action.type) {
    case "patch":
      return { ...state, ...action.formData };
    case "reset":
      return initFormData;
    default:
      throw new Error();
  }
};

const App = () => {
  console.log("aaa");
  const [formData, dispatch] = useReducer(reducer, initFormData);
  const onSubmit = () => {};
  const onReset = () => {
    dispatch({ type: "reset" });
  };
  return (
    <form onSubmit={onSubmit} onReset={onReset}>
      <div>
        <label>
          姓名
          <input
            value={formData.name}
            onChange={(e) =>
              dispatch({
                type: "patch",
                formData: { name: e.target.value },
              })
            }
          />
        </label>
      </div>
      <div>
        <label>
          年龄
          <input
            value={formData.age}
            onChange={(e) =>
              dispatch({
                type: "reset",
                formData: { age: e.target.name },
              })
            }
          />
        </label>
      </div>
      <div>
        <label>
          民族
          <input
            value={formData.nationality}
            onChange={(e) =>
              dispatch({
                type: "patch",
                formData: { nationality: e.target.value },
              })
            }
          />
        </label>
      </div>
      <div>
        <button type="submit">提交</button>
        <button type="reset">重置</button>
      </div>
      <hr />
      {JSON.stringify(formData)}
    </form>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));


如何代替 Redux

步骤:

  1. 将数据集中在一个 store 对象
  2. 将所有操作集中在 reducer
  3. 创建一个 Context
  4. 创建对数据的读写 API
  5. 将第四步的内容放到第三步的 Context
  6. 用 Context.Provider 将 Context 提供给所有组件
  7. 各个组件用 useContext 获取读写 API

demo ,模块化后 demo


useContext

上下文

全局变量是全局的上下文

上下文是局部的全局变量

使用:

  1. 使用 C = createContext(initial) 创建上下文
  2. 使用 <C.provider> 圈定作用域
  3. 在作用域内使用 useContext(C) 来使用上下文

demo

注意:

useContext 不是响应式的。

在一个模块将 C 里的值改变,另一个模块不会感知到这个变化。


useEffect

副作用

  • 对环境的改变即为副作用,如修改 document.title
  • 但我们不一定非要把副作用放在 useEffect 里
  • 实际上叫做 afterRender 更好,因为每次 render 后会执行

用途:

  • 作为 componentDidMount 使用, [] 作第二个参数
  • 作为 componentDidUpdate 使用,可指定依赖
  • 作为 componentWillUnmount 使用,通过 return
  • 以上三种用途可同时存在

特点:如果同时存在多个 useEffect,会按照出现次序执行。

demo

useLayoutEffect

布局副作用

useEffect 在浏览器渲染完成后执行,demo

useLayoutEffect 在浏览器渲染前执行,通过时间点来侧面证明

特点:

useLayoutEffect 总是比 useEffect 先执行,demo

useLayoutEffect 里的任务最好影响了 Layout,不然没必要用 useLayoutEffect。

经验:

为了用户体验,优先使用 useEffect(优先渲染)


useMemo

要理解 React.useMemo,需要先了解 React.memo,React 默认有多余的 render,demo,代码中的 Child 用 React.memo(Child)代替,如果 props 不变,就不会再次执行函数组件。

useMemo 的作用就是提供了一个 memorize 值,在依赖项改变之后,该值才会改变才会被重新计算。demo

特点:

  • 第一个参数是 ()=>value

  • 第二个参数是依赖 [m, n]

只有当依赖变化时,才会计算出新的 value,如果依赖不变,那么就重用之前的 value。类似于 vue2 的 computed。

注意:如果 value 是个函数,那么就要写成 useMemo( ()=> (x)=>console.log('1') ),这是一个返回函数的函数,

于是就有了 useCallback,useCallback( (x)=>console.log('1') )


useCallback

用法:

useCallback(x => console.log(x), [m])
等价于
useMemo( () => x=>console.log(x), [m])

useRef

目的:

如果你需要一个值,在组件不断 render 时保持不变

初始化:const count = useRef(0)code

读取:count.current

为什么需要 current ?

为了保证两次 useRef 是同一个值(只有引用能做到)

useRef 不会自动更新 UI。

拓展:

vue3 的 ref,

初始化:const count = ref(0)

读取:count.value

不同点:Vue3 会自动 render。

forwardRef

demo1:props 无法传递 ref 属性

demo2:实现 ref 的传递

demo3:两次 ref 传递得到 button 的引用

const App = () => {
  const buttonRef = useRef(null);
  console.log("buttonRef", buttonRef);
  return (
    <div>
      <h1>React.forwardRef</h1>
      <hr />
      <div>
        <Button3 ref={buttonRef}>button</Button3>
      </div>
    </div>
  );
};
const Button2 = (props, ref) => {
  console.log(props);
  console.log(ref);
  return <button className="red" ref={ref} {...props} />;
};
// 实现 ref 的传递
const Button3 = forwardRef(Button2);

// const Button4 = forwardRef((props, ref) => {
//   console.log(props);
//   console.log(ref);
//   return <button className="red" ref={ref} {...props} />;
// });

useRef:

  • 可以用来引用 DOM 对象
  • 也可以用来引用 普通对象

forwardRef:

  • 由于 props 不包含 ref,所以需要 forwardRef
  • 为生命 props 不包含 ref ?以为大部分时候不需要。

useImperativeHandle

用于自定义 ref 的属性。推荐阅读

demo1 使用 useImperativeHandle

demo2 不使用 useImperativeHandle

// 子组件
const ChildComponent = forwardRef((props, ref) => {
  const [count, setCount] = useState(0); //子组件定义内部变量count
  //子组件定义内部函数 addCount
  const addCount = () => {
    setCount(count + 1);
  };
  //子组件通过useImperativeHandle函数,将addCount函数添加到父组件中的ref.current中
  useImperativeHandle(ref, () => ({ addCount }));
  return (
    <div>
      {count}
      <button onClick={addCount}>child</button>
    </div>
  );
});

// 父组件
function Imperative() {
  const childRef = useRef(null); //父组件定义一个对子组件的引用

  const clickHandle = () => {
    childRef.current.addCount(); //父组件调用子组件内部 addCount函数
  };

  return (
    <div>
      {/* 父组件通过给子组件添加 ref 属性,将childRef传递给子组件,
            子组件获得该引用即可将内部函数添加到childRef中 */}
      <ChildComponent ref={childRef} />
      <button onClick={clickHandle}>child component do something</button>
    </div>
  );
}

function App() {
  return <Imperative />;
}

自定义 Hook

封装数据操作

简单例子,贴心例子

还可以在自定义 Hook 里使用 Context

useState 只说了不能在 if 里,没说不能在函数里运行,只要这个函数在函数组件里运行即可。

Stale Closure

过时闭包

参考文章链接


Snipaste_2021-11-29_23-27-35
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值