React Hooks详解

84 阅读5分钟

React Hooks

image.png

useState

使用状态

  • const[n,setN] = React.useState(0)--初始值数字

  • const[user,setUser] = React.useState({name:'F'})--对象

注意事项1: 不可局部更新

  • 如果state是一个对象,不能部分setState,因为setState不会帮我们合并属性

image.png

点击click之后,并不会将age:18展示出来,如果想要age:18展示出来,加上...user(把所有user属性拷贝出来,用name属性覆盖之前的name属性)

image.png

注意事项2:地址要变

  • setState(obj)如果地址不变,那么React就认为数据没有变化
const onClick = () =>{
  user.name = "Jack"
  setUser(user)
}//是不会在页面上有任何变化的

//不能是之前的对象,必须是新的对象必须改变它的的地址
const onClick = () =>{
  setUser({
    ...user,
    name: 'Jack'
  })
}

useState接受函数

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

  • useState接受函数的好处是,减少计算的过程

setState接受函数

  • setN(i => i + 1)

useReducer

用来践行Flux/Redux的思想

分四步走

  • 一、创建初始值initialState

  • 二、创建所有的操作reducer(state,action)

  • 三、传给useReducer,得到读和写API

  • 四、调用写({type:'操作类型'})

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() {
  const [state, dispatch] = useReducer(reducer, initial);
  const { n } = state;
  const onClick = () => {
    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>//第四步
  );
}

如何代替Redux

  • 一、将数据集中在一个store对象

  • 二、将所有操作集中在reducer

  • 三、创建一个Context

  • 四、创建对数据的读写API

  • 五、将第四步的内容放到第三步的Context

  • 六、用Context.ProviderContext提供给所有组件

  • 七、各个组件用useContext获取读写API

代码

模块化后的代码

useContext

上下文

  • 全局变量是全局的上下文

  • 上下文是局部的全局变量

使用方法

  • 一、使用C=createContext(initial)创建上下文

  • 二、使用<C.Provider>圈定作用域

  • 三、在作用域内使用useContext(C)来使用上下文

const C = createContext(null)

function App() {
  console.log("APP 执行了")
  const [n, setN] = useState(0)
  return (
    <C.Provider value={{ n, setN}}>
      <div className="App">
        <Baba />
      </div>
    </C.Provider>
  )
}

function Baba() {
  const { n, setN } = useContext(C)
  return (
    <div>
      我是爸爸 n: {n} <Child />
    </div>
  )
}
function Child() {
  const { n, setN } = useContext(C)
  const onClick = () =>{
    setN(i => i+1)
  }
  return (
    <div>
      我是儿子 我得到的 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  )
}

image.png

useContext注意事项

不是响应式

  • 你在一个模块将C里面的值改变

  • 另一个模块不会感知到这个变化

useEffect

function App(){
  const [n, setN] = useState(0)
  const onClick = () =>{
    setN(i => i+1)
  }
  useEffect(()=>{
    console.log("第一次渲染之后执行这一句话")
  },[]) //里面的变量变化时执行 => 不会执行
  useEffect(()=>{
    if(n !== 0){
      console.log("n变化了")
    }
  },[n]) //n变化时执行
  useEffect(()=>{
    console.log("任何一个state变化时都执行")
  })
  return (
    <div>
      n:{n}
      <button onClick={onClick}>+1</button>
    </div>
  )

副作用(API名字叫得不好)

  • 对环境的改变即为副作用,如修改document.title

  • 但我们不一定非要把副作用放在useEffect

  • 实际上叫做afterRender更好,每次render后运行

用途

  • 作为componentDidMount使用,[]作第二个参数

  • 作为componentDidUpdate使用,可指定依赖

  • 作为componentWillUnmount使用,通过return

  • 以上三种用途可同时存在

特点

  • 如果同时存在多个useEffect,会按照出现次序执行

useLayoutEffect

布局副作用

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

  • useLayoutEffect在浏览器渲染前执行

image.png

const App = () => {
  const [n, setN] = useState(0);
  useEffect(() => {
    document.querySelector('#x').innerText = `n: 1000`
  }, [n]);
  return (
    <div id="x" onClick={() => setN(0)}>n: {n}</div>
  );
};

特点

  • useLayoutEffect总是比useEffect先执行

  • useLayoutEffect里的任务最好影响了Layout

经验

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

useMemo

image.png

代码

特点

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

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

  • 只有当依赖变化时,才会计算出新的value

  • 如果依赖不变,那么就重用之前的value(类似于Vue2的computed)

注意:

  • 如果你的value是个函数,那么你就要写成useMemo(()=> (x) => console.log(x))

  • 这是一个返回函数的函数(难用,所以就有了useCallback

useCallback

  • 用法:useCallback(x => log(x),[m])等价于useMemo(()=> x =>log(x), [m])

useRef

目的

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

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

  • 读取: count.current

  • 为什么需要current为了保证两次useRef是同一个(只有引用能做到)

image.png

  • 注意:count还是同一个count,但是count对应的current不是同一个

image.png

延伸(Vue3的ref)

  • 初始化:const count=ref(0)

  • 读取:count.value

  • 不同点:当count.value变化时,Vue3会自动render

useRef能做到变化时自动render吗?

  • 不能,因为不符合React的理念

  • React的理念是UI=f(date)

  • 但是可以自己添加ref,当ref.current变化时,调用setX即可

function App() {
  console.log("App 执行")
  const [n, setN] = React.useState(0)
  const [_, set_] = React.useState(0)
  const onClick = () =>{
    setN(n +9 )
  }
  const onClick2 = () =>{
    count.current += 1
    //set_(Math.rsndom())
    console.log(count.current)
  }
  useEffect(()=>{
    console.log(count.current)
  })
  return (
    <div className="App">
      <div>
      <button onClick ={onClick}>update n {n}</button>
      <button onClick2 ={onClick2}>update count: {count.current}</button>
      </div>
    </div>
  )
}

forwardRef

讲了useRef了解一下forwardRef

  • props无法传递ref属性
function App() {
  const buttonRef = useRef(null)
  return (
    <div className="App">
      <Button2 ref={buttonRef}>按钮</Button2>
      {/*浏览器控制台的报错*/}
    </div>
  )
}
const Button2 = props =>{
  return <button className="red" {...props}/>
}
  • 实现ref的传递
function App() {
  const buttonRef = useRef(null)
  return (
    <div className="App">
      <Button3 ref={buttonRef}>按钮</Button3>
    </div>
  )
}
const Button3 = React.forwardRef((props, ref) => {
  return <button className="red" ref={ref} {...props}/>
})

useRef

  • 可以用来引用DOM对象

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

forwardRef

  • 由于props不包括ref,所以需要forwardRef

  • 为什么props不包含ref呢?因为大部分时候不需要

uselmperativeHandle

  • 应该叫做setRef
function App() {
  const buttonRef = useRef(null);
  useEffect(() => {
    console.log(buttonRef.current);
  });
  return (
    <div className="App">
    <Button2 ref={buttonRef}>按钮</Button2>
    <button
      className="close"
      onClick={() => {
        console.log(buttonRef);
        buttonRef.current.x();
      }}
    >
      x
    </button>
    </div>
  );
}
const Button2 = React.forwardRef((props, ref) => {
  const realButton = createRef(null);
  const setRef = useImperativeHandle;
  setRef(ref, () => {
    return {
      x: () => {
        realButton.current.remove();
      },
      realButton: realButton
    };
  });
  return <button ref={realButton} {...props} />;
});

image.png

  • 用于自定义ref的属性

自定义Hook

封装数据的操作

image.png

代码1 代码2

分析

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

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

Stale Closure

  • 过时的闭包

参考文章