likes
comments
collection
share

Vue3 与React18的区别深度解析

作者站长头像
站长
· 阅读数 18

章节介绍

小伙伴大家好,本章将学习React18与Vue3全方面对比 - React与TS结合开发。

本章学习目标

本章将全面对比React与Vue两大框架的相似点和差异点,通过对比的方式,可以让同学们更加快速的掌握两大框。

详细讲解React与TS配合使用,包括路由、状态管理等TS配合。

课程安排

  • React18与Vue3对比之编程风格与视图风格
  • React18与Vue3对比之组件-路由-状态管理等风格
  • React18与Vue3对比之模板-样式-事件-表单等功能
  • React18与Vue3对比之组件通信-逻辑复用-内容分发-DOM操作等功能
  • React18与Vue3对比之diff算法的相同策略与不同策略
  • React18与Vue3对比之响应式-生命周期-副作用等功能
  • React与TS配合之基础props限制
  • React与TS配合之children与event限制
  • React与TS配合之style与component限制
  • React与TS配合之use函数限制
  • React与TS配合之类组件类型限制
  • React Router路由如何使用TS进行开发
  • Redux Toolkit状态管理如何使用TS进行开发

React18与Vue3对比之编程风格与视图风格

编程风格对比

React语法少,难度大

Vue语法多,难度小

例如:在react中是没有指令的,所有的功能都要通过原生JavaScript来实现。所以说当使用Vue框架的时候,真的就是在操作Vue,而使用React框架其实更贴近于原生JS操作。

举一个实例:比如表单操作的时候,在Vue中可以直接通过v-model指定进行操作。而React则比较麻烦,需要通过受控组件的方式给表单添加value和onChange配置来进行操作。

<!-- vue -->
<input v-model="username">
<!-- react -->
<input value={username} onChange={(ev)=> setUsername(ev.target.value)} />

视图风格对比

Vue采用<template>字符串模板。更贴近HTML,学习成本低,但有时候不灵活。

React采用JSX语法,限制比较多,但是可以跟模板语法很好的进行结合。

所以两个框架各有优缺点。

下面看一下JSX灵活的地方。

<!-- vue -->
<template>
    <h1 v-if="leave === 1">
        标题1
    </h1>
    <h2 v-if="leave === 2">
        标题2
    </h2>
</template>
<!-- react -->
let Welcome = () => {
    const leave = 1;
    const Tag = 'h' + leave;
    return (
    	<div>
        	{ <Tag>标题{leave}</Tag> }
        </div>
    );
}

React18与Vue3对比之组件-路由-状态管理等风格

组件风格对比

Vue采用选项式API,组合式API

React采用类组件,函数组件

两个框架的组件风格差异还是蛮大的,但是仔细分析会发现,其实组合式API和函数组合是非常相近的,都是面向函数进行编程的,比面向对象编程会更加的简单,而且也更加的灵活。

组件都是采用use函数的方式进行功能复用。

路由对比

Vue语法更加简练

React的use函数太多,不够统一化

这两个框架对于常见的路由需求都是可以灵活实现的,例如:路由表、嵌套、动态、编程式、守卫等。

所以总结来说,Vue的路由使用起来会更加的灵活;React使用起来会略显复杂。并且Vue功能更加丰富,而React有些需求需要自己模拟去实现。

状态管理对比

Vue采用Vuex或Pinia

React采用Redux或Mobx

Vue中推荐使用Pinia,因为使用更加简单,并且跟Vue3的组合式配合会更加的统一。

React中推荐使用Redux的扩展版本,Redux-toolkit(RTK),使用起来跟Vue对比还是略显复杂一些,不过常见的功能都可以实现的很好:共享状态、同步、异步、模块化、持久化等。

# React18与Vue3对比之模板-样式-事件-表单等功能

模板对比

Vue的视图变化,主要通过:指令 + 模板的方式

React的视图变化,主要通过:原生JS + 模板的方式

React的模板比较强大,因为可以编写JSX结构,所以可以做出更加灵活的结构处理。

样式对比

Vue的class和style都有三种写法:字符串、数组、对象

React的style只能写对象,class只能字符串,可借助classnames这个库

两个框架基本上都可以满足常见的样式需求。

事件对比

Vue事件功能丰富

React事件传参需要高阶处理

<!-- Vue -->
<template>
    <ul>
        <li v-for="item,index in list" @click="handleClick(index)"></li>
    </ul>
</template>
<script>
methods: {
	handleClick(index){
	
    }
}
</script>
<!-- React -->
<ul>
{ 
    list.map((v, i)=> <li onClick={handleClick(i)}></li>)
}
</ul>
const handleClick = (index) => {
    return () => {
        console.log(index)
    }    
}

表单对比

Vue表单双向绑定v-model

React表单受控与非受控

针对表单操作这一块来说,Vue的表单指令v-model还是非常灵活的,总体对比要比React使用方便且灵活。

React18与Vue3对比之组件通信-逻辑复用-内容分发-DOM操作等功能

组件通信对比

Vue父子props,子父emits

React父子props,子父回调函数

emits自定义事件和回调函数,但实际上是一样的思想。

跨组件的通信方案也很类似,都是一种依赖注入的方式来实现的。

功能复用处理

Vue选项式采用:mixins混入;组合式采用:use函数

React类组件采用:Render Props、HOC;函数组件:use函数

可以发现组合式API和函数组件都是采用use函数,所以基本复用是差不多的思想,这也是两个框架推荐的用法。

内容分发处理

Vue通过<slot>插槽,进行接收

React通过props.children,进行接收

原生DOM处理

Vue通过ref属性

React也通过ref属性处理

思路都是差不多的,就是给元素添加ref属性,在跟对象或字符串绑定在一起,这样就可以直接获取到DOM元素。

React18与Vue3对比之diff算法的相同策略与不同策略

diff算法的相同策略

首先两个框架都是采用了同层级对比的方式,这样可以大大减少对比的次数。

Vue3 与React18的区别深度解析

当两个节点进行对比的时候,要分不同的情况进行处理。

Vue3 与React18的区别深度解析

大部分的策略都是一样的,只有当两个字节都存在子节点的时候,对比方案才有所区别。

diff算法的不同策略

下面是一个具体的案例,来分别看一下Vue和React是如何进行处理的。

Vue3 与React18的区别深度解析

下面先看一下Vue的策略,即数组格式,首尾对比,最长递增子序列。

Vue3 与React18的区别深度解析

再看一下React的策略,即r链表格式,从左到右,索引比较。

Vue3 与React18的区别深度解析

React18与Vue3对比之响应式-生命周期-副作用等功能

响应式数据对比

Vue采用响应式数据,底层通过new Proxy()进行监控,灵活性更高

React采用state状态,通过setState()方法进行内部re-render,可控性更强

生命周期对比

Vue生命周期钩子(常见)

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

React生命周期钩子(常见)

  • constructor
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount
  • render

整体对比来看,Vue的生命周期会更丰富一些,React生命周期会更简约一些。

副作用处理对比

vue使用,watchEffect()

react使用,useEffect()

都是处理副作用的方法,用法上还是有很大区别的。

watchEffect会自动根据所依赖的值进行重渲染,而useEffect要明确指定对应的值才能进行重渲染,React团队已经给出在未来的版本中可能会改成根据所依赖的值自动进行重渲染的操作,但暂时还不行。

watchEffect在更新前和卸载前触发的方式是通过回调函数的参数被调用来实现的,而useEffect是通过return的返回值来指定的。

// Vue
watchEffect((cb)=>{
	cb(()=>{
       //更新前的触发
    })
})
// React
useEffect(()=>{
   return ()=>{
      //更新前的触发
   }
})

React与TS配合之基础props限制

对父子通信进行类型限定

首先让脚手架支持TypeScript,可以在安装脚手架的时候进行配置即可,命令如下。

npx create-react-app react-ts-study --template typescript

然后就是创建两个组件,并且完成props通信。

import React from 'react'
interface WelcomeProps {
  msg?: string
  count?: number
  list: string[]
  info: { username: string; age: number }
  status?: 'loading' | 'success' | 'error'
}

function Welcome(props: WelcomeProps) {
  const { count = 0 } = props;
  return (
    <div>
      <h2>hello Welcome, {count}</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>01_react-ts</h2>
      <Welcome msg="hello" count={123} list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
      <Welcome list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
      <Welcome status="loading" list={['a', 'b', 'c']} info={{username: 'xiaoming', age: 20}} />
    </div>
  )
}

下面来看一下函数表达式写法的情况下,如何指定props的类型,可通过内置的FC类型来进行实现。

const Welcome: React.FC<WelcomeProps> = (props) => {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}

React与TS配合之children与event限制

children的类型限制

父子通信时候的内容分发进行限制。

import React from 'react'
interface WelcomeProps {
  children?: React.ReactNode
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome, {props.children}</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>02_react-ts</h2>
      <Welcome />
      <Welcome>
        aaaaa
      </Welcome>
    </div>
  )
}

我们把children属性作为可选参数,这样当组件进行内容分发和不进行内容分发都是可以的。

event限制

event在React中主要通过内置的ev: React.MouseEvent来进行限定。

import React from 'react'
interface WelcomeProps {
  children?: React.ReactNode
  handleMsg?: (ev: React.MouseEvent<HTMLButtonElement>)=> void
}
function Welcome(props: WelcomeProps) {
  const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
    console.log(ev.target.value)
  }
  return (
    <div>
      <h2>hello Welcome, {props.children}</h2>
      <button onClick={props.handleMsg}>点击</button>
      <input type="text" onChange={handleChange} />
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>02_react-ts</h2>
      <Welcome />
      <Welcome handleMsg={(ev)=>{}}>
        aaaaa
      </Welcome>
      
    </div>
  )
}

React与TS配合之style与component限制

style限制

当我们进行style样式通信的时候,也是可以指定类型,防止样式传递的时候不复合规范。

import React from 'react'
interface HeaderProps {
  username: string
}
interface WelcomeProps {
  style: React.CSSProperties
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}
export default function App() {
  return (
    <div>
      <h2>03_react-ts</h2>
      <Welcome style={{'border': '1px red solid', display: 'none'}} />
    </div>
  )
}

主要通过React.CSSProperties来指定样式的类型,这样当传递的样式属性或者值不符合规范的时候,就不会产生TS的提示。

component限制

如果组件进行通信的时候,也可以进行类型的限制。

import React from 'react'
interface HeaderProps {
  username: string
}
interface WelcomeProps {
  style: React.CSSProperties
  component: React.ComponentType<HeaderProps>
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
      <props.component username="xiaoming"></props.component>
    </div>
  )
}
function Header(props: HeaderProps) {
  return (
    <div>hello Header</div>
  )
}
export default function App() {
  return (
    <div>
      <h2>03_react-ts</h2>
      <Welcome style={{'border': '1px red solid', display: 'none'}} component={Header} />
    </div>
  )
}

主要通过React.ComponentType<>来指定组件的类型,那么一旦不符合指定的接口类型,就会报错。

React与TS配合之use函数限制

use函数限制

在React函数组件中,主要就是对use函数进行类型的注解。常见的注解use函数如下:

  • useState -> 联合类型、对象字面量类型
  • useEffect -> 自动类型推断
  • useRef -> 泛型标签类型
import React, { useEffect, useState, useRef } from 'react'
interface WelcomeProps {
}
function Welcome(props: WelcomeProps) {
  return (
    <div>
      <h2>hello Welcome</h2>
    </div>
  )
}
type Info = {username: string; age: number}
export default function App() {
  //const [count, setCount] = useState(0)
  const [count, setCount] = useState<number|string>(0)
  const [list, setList] = useState<string[]>([])
  //const [info, setInfo] = useState<{username: string; age: number}|null>(null)
  const [info, setInfo] = useState<Info>({} as Info)
  const myRef = useRef<HTMLButtonElement>(null)
  useEffect(()=>{
    console.log( myRef.current?.innerHTML )  // 可选链(类型保护)
    //console.log( myRef.current!.innerHTML )  // 非空断言(慎用)   
    return ()=>{
    }
  }, [])
  const handleClick = () => {
    setCount(1)
    setList(['a', 'b'])
  }
  return (
    <div>
      <h2>04_react-ts</h2>
      <button onClick={handleClick} ref={myRef}>点击</button>
      { info.username }, { info.age }
      <Welcome />      
    </div>
  )
}

useState和useRef都是通过泛型的方式进行类型注解,useEffect主要利用自动类型推断来完成。

React与TS配合之类组件类型限制

类组件类型限定

类组件在React中并不是重点,但是也要了解怎么对类组件进行类型的限制。

import React, { Component } from 'react'
interface WelcomeProps {
  msg: string
  count: number
}
interface WelcomeState {
  username: string
}
class Welcome extends Component<WelcomeProps, WelcomeState> {
  state = {
    username: 'xiaoming'
  }
  render() {
    return (
      <div>hello Welcome {this.state.username}</div>
    )
  }
}
export default function App() {
  return (
    <div>
      <h2>05_react-ts</h2>
      <Welcome msg="hello" count={123} />      
    </div>
  )
}

主要就是给继承的类Component传递泛型,Props和State,这样可以实现父子通信的数据进行类型限制,又可以对内部的state进行类型限制。

# React Router路由如何使用TS进行开发

react-router-dom类型限制

React路由与TS配合常见的使用为以下这些操作:

  • RouteObject 内置类型,限制路由表
  • React.createElement() 进行组件编写
  • 扩展 meta 元信息
// /router/index.ts
import { createBrowserRouter } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
import App from '../App';
import Index from '../views/Index/Index';
import User from '../views/User/User';
import Login from '../views/Login/Login';
import React from 'react';
declare module 'react-router' {
  interface NonIndexRouteObject {
    meta?: { title: string }
  }
  interface IndexRouteObject {
    meta?: { title: string }
  }
}
export const routes: RouteObject[] = [
  {
    path: '/',
    element: React.createElement(App),
    meta: { title: '/' },
    children: [
      {
        path: 'index',
        element: React.createElement(Index),
        meta: { title: 'index' }
      },
      {
        path: 'user',
        element: React.createElement(User),
        meta: { title: 'user' }
      },
      {
        path: 'login',
        element: React.createElement(Login)
      }
    ]
  }
];
const router = createBrowserRouter(routes);
export default router;
# Redux Toolkit状态管理如何使用TS进行开发

Redux Toolkit限制类型

Redux状态管理与TS配合常见的使用为以下这些操作:

  • 得到全局state类型: ReturnType
  • 限定payload类型: PayloadAction
// /store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/user';
import { useDispatch } from 'react-redux'
const store = configureStore({
  reducer: {
    user: userReducer
  }
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
export default store;
// /store/modules/user.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
export const loginAction = createAsyncThunk(
  'users/loginAction',
  async (userId: number) => {
    const response = await new Promise((resolve)=>{
      resolve('response data')
    })
    return response
  }
)
const userSlice = createSlice({
  name: 'user',
  initialState: {
    name: 'xiaoming'
  },
  reducers: {
    change(state, action: PayloadAction<string>){
      state.name = action.payload
    }
  }
})
export const { change } = userSlice.actions
export default userSlice.reducer