如何快速从vue转react hook开发

1,159 阅读6分钟

背景

这篇文章应该非常实用,目标群体就是vue技术栈转react hook的开发人员。其实最关键的就是一个开发思路的转变,快速上手的方法就是原来我们在vue里的用到的生命周期、watch、computed等等功能对应到react hook中应该怎么写。

React Hook介绍

毕竟读这篇文章的是vue的开发人员,可能都没有了解过react hook,所以说几点概括性的总结方便理解。

  • Hook 是 React 16.8 的新增特性。
  • Hook写组件的方式和原来的Class写法是完全兼容的,只是多了一种写组件的方式。
  • Hook给函数组件带来的状态管理的能力。
  • Hook的出现和vue3的组合式API动机一样,都是能更好的抽象逻辑,方便复用和维护。

看一下Hook的所有API 列表:

不要慌,掌握以下4个就能应付大部分常规场景

Hook实现vue中的常用选项

data

vue中的data是响应式的数据,也就是data变化的时候组件可以重新渲染。 一个加减计数器的功能.

<template>
  <h1>count: {{count}}</h1>
  <button @click="add">add</button>
   <button @click="reduce">reduce</button>
</template>
<script>
export default {
  name: "App",
  data: function() {
    return {
      count: 0
    }
  },
  methods: {
    add() {
      this.count++
    },
    reduce() {
      this.count--
    }
  }
};
</script>

对应到Hook中用useState实现。 如果不用useState 可能会这么写:


export default function App() {
    let count = 0
    return (
        <div className="App">
        <h1>count: {count}</h1>
        <button onClick={()=>count++}>add</button>
        <button onClick={()=>count--}>reduce</button>
        </div>
    );
}

问题来了,count就是普通变量,变化不会让这个函数组件重新渲染,就算重新渲染了作为普通的函数,函数重新执行了count也会重新变成0,不记录上一次的结果。useState就是解决这个问题的。


import {useState} from 'react'
export default function App() {
  const [count, setCount] = useState(0)
  return (
    <div className="App">
      <h1>count: {count}</h1>
      <button onClick={()=>setCount(count+1)}>add</button>
      <button onClick={()=>setCount(count-1)}>reduce</button>
    </div>
  );
}

useState接收状态的初始值,返回当前值和设置该值的方法。state的值变化时函数会重新渲染。函数在重新渲染时最新的值也能保留,理解成在该函数的外侧为这个组件记录了变量,所以变量的生命周期能不在该函数范围内

computed

在data例子的基础上增加一个computed属性doubleCount展示count的2倍。

<template>
  <h1>count: {{count}}</h1>
  <h1>double count: {{doubleCount}}</h1>
  <button @click="add">add</button>
   <button @click="reduce">reduce</button>
</template>
<script>
export default {
  name: "App",
  data: function() {
    return {
      count: 0
    }
  },
  computed: {
    doubleCount() {
      return this.count*2
    }
  },
  methods: {
    add() {
      this.count++
    },
    reduce() {
      this.count--
    }
  }
};
</script>

对应到Hook中可以用普通变量或者useMemo实现, 普通变量就是每次渲染都会重新执行,用useMemo的意义是可以缓存计算结果,第二个参数传入依赖项,只有第二个参数传入的依赖项变化时,才会重新计算,这样避免每次渲染都重新计算,和vue中的computed的作用是相似的,缓存计算结果,只不过vue中可以自动分析依赖,useMemo需要手动指定。

import {useState, useMemo} from 'react'
export default function App() {
  const [count, setCount] = useState(0)
  const doubleCount = useMemo(()=>count*2,[count])
  // const doubleCount = count*2
  return (
    <div className="App">
      <h1>count: {count}</h1>
      <h1>doubleCount: {doubleCount}</h1>
      <button onClick={()=>setCount(count+1)}>add</button>
      <button onClick={()=>setCount(count-1)}>reduce</button>
    </div>
  );
}

methods

vue中的methods就是声明能给模版中使用的的函数,对应到Hook中可以使用普通函数和useCallBack实现,和useMemo类似,普通函数就是每次渲染都创建新的函数,useCallBack的意义就是能够指定在依赖项的值变化时才会重新创建新的函数。这对react中一些根据引用值判断子组件是否重新渲染比较有用,也属于性能优化的方法。

import {useState,  useCallback} from 'react'
export default function App() {
  const [count, setCount] = useState(0)

  function add() {
    setCount(count+1)
  }
  const reduce = useCallback(()=>{
    setCount(count-1)
  },[count])
  return (
    <div className="App">
      <h1>count: {count}</h1>
      <button onClick={add}>add</button>
      <button onClick={reduce}>reduce</button>
    </div>
  );
}

生命周期

vue中几个常用的就三个生命周期 mounted、updated、unmounted, 。对应使用的Hook是useEffectuseEffect有2个参数,第一个参数是执行的函数,第二个参数是执行的依赖。执行的时机是组件渲染到屏幕之后执行,如果第二个参数没有,那就每次渲染都执行,如果第二个参数指定了依赖,那就依赖变化的时候执行,如果第二个依赖是空数组,那就只有第一次渲染的之后执行。 useEffect的返回值是当第一个参数重新执行之前会执行上一次的返回值函数,用于清除上一次执行的副作用。

对应生命周期的实现如下,beforeCreated和created本身用的场景也不多,mounted就够了。 这个里边还用了一个Hook 是 useRef, 这个Hook可以理解为就是在this上创建了一个变量,记录状态。和useState的区别就是它不是响应式的。

import { useState, useEffect, useRef, memo } from "react";
export default function Com(props){
  const {count} = props
  // beforeCreated  拿不到任何状态,基本不用
  const beforeCreated = useRef(false);
  if (!beforeCreated.current) {
    console.log("beforeCreated");
    beforeCreated.current = true;
  }

  const [stateA] = useState(0);
  // created可以拿到初始状态
  const created = useRef(false);
  if (!created.current) {
    console.log("created", stateA);
    created.current = true;
  }

  const mounted = useRef(false);
  useEffect(() => {
    if (mounted.current) {
      console.log("mounted之后的 effect 是updated");
    }
  });

  //mounted在第一次渲染之后
  useEffect(() => {
    mounted.current = true;
    console.log("mounted");
    return () => {
      // 卸载组件会执行useEffect的返回函数
      console.log("unmounted");
    };
  }, []);

  return <h1>sub component{stateA} {count}</h1>;
}

还有一个问题是vue对自动识别组件依赖的属性而出发updated,而react默认是父组件渲染子组件也会渲染,不会判断子组件是否没变化, 所以就会额外的出发updated, 我们可以使用React.memo方法可以做优化, 默认只有当属性的值变化时才会触发子组件的渲染。

import { memo } from "react";
export default memo(props=>{
    
})

watch

vue中默认的immediate是false,也就是第一次不会执行,只有当值变化时才会执行。

watch: {
    // count:function(oldValue, newValue) {
    //   console.log(oldValue, newValue)
    // },
    count: {
      immediate: true,
      handler: function (newValue, oldValue) {
        console.log("watch count", newValue, oldValue);
      },
    },
 },

实现watch功能还是使用useEffect,使用useEffect的第二个参数,指定依赖执行函数。

  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("watch count", count);
  }, [count]);

通过判断是否执行过一次来控制让其在第一次不执行:

  const [count, setCount] = useState(0);
  const mounted = useRef(false);
  useEffect(() => {
    if (!mounted) return;
    console.log("watch count", count);
    setOldCount(count);
  }, [count]);
  useEffect(() => {
    mounted.current = true;
  }, []);

通过手动存储来获取改变前的值:

  const mounted = useRef(false);
  const [oldCount, setOldCount] = useState(count);
  useEffect(() => {
    if (!mounted) return;
    if (count === oldCount) return;
    console.log("watch count", count, oldCount);
    setOldCount(count);
  }, [count, oldCount, setOldCount]);

  //mounted在第一次渲染之后
  useEffect(() => {
    mounted.current = true;
  }, []);

总结

本文先介绍了下Hook是什么,然后详细讲解了如果在Hook中如何实现vue中的data、computed、methods、生命周期、watch,这也是vue开发中最常用的几个选项,能覆盖大部分常规开发的场景,当然关于Hook的更多学习和理解还是需要去官网看详细文档。

  • 如果觉得有用请帮忙点个赞。
  • 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿