Vue-React

概述

组成项

配置项vue函数式vue函数式react

数据

组件内定义

data

$ref

computed

template||render

reactive

ref

computed

template||render

useState

useRef

useMemo,useCallback

JSX

数据

父组件传入

props,attr,slot

provider-inject

自定义事件

defineProps,attr,slot

provider-inject

自定义事件

props

useContext

数据

import引入

配置components

无需配置无需配置
向上暴露ref+defineExpose

forwardRef+useImperativeHandle

方法methodsfunctionfunction
钩子

生命周期

watch

$nextTick

生命周期

watch

$nextTick

组件函数

useEffect

插件

router(路由)

pinia(仓库)

router(路由)

pinia(仓库)

route(路由)

redux(仓库)

Vue的ref能指向普通标签实例和组件实例

函数式React的ref只能指向普通标签实例,因为函数式组件根本没有实例

用法对比

VueReact
ref,reactiveuseState
模版语法:{{}},v-bind{}
v-bind:style="{k1:v1}"style={{k1:v1}}
v-bind:class="样式名"className={样式名}

v-model="textValue"

value={textValue}

onChange=(e)=>{...}

v-if

show?< 标签名/>:<></>

show&&< 标签名/>

v-showstyle={{display:'none'}}
v-for

obj.map((el,index)=>{

    return ...

})

watch,生命周期useEffect
props=defineProps(["变量1","变量2"])props通过函数参数传递

Test组件的内容:

<div>

    <slot name='one' />

</div>

Test组件的使用

<Test>

    <template

        v-slot:'one'

    >

        ....

    </template>

</Test>

Test组件的内容:

<div>

    props.childen.find(

      child=>child.key=='one'

    )

</div>

Test组件的使用:

<Test>

    <Fragement 

        key={'one'}

    >

       ...

    </Fragement>

</Test>

事件绑定

HTML部分实际转化
原生

<div

 οnclick="test()"

/>

<div

 οnclick="age++"

/>

div.οnclick=(e)=>{}

div.οnclick=(event)=>{

    test()

}

div.οnclick=(event)=>{

    age++

}

div.οnclick=(e)=>{}

Vue

<div

 @click="fun1()"

/>

<div

  @click="age++"

/>

<div

  @click="fun2"

/>

div.οnclick=($event)=>{

    fun1()

}

div.οnclick=($event)=>{

    age++

}

div.οnclick=fun2

React

<div

  onClick={fun1}

/>
<div

  onClick={

    fun2("hi")

  }

/>

<div

  onClick={(e)=>{

    fun3("hi")

  }}

/>

div.οnclick=fun1

div.οnclick=fun2("hi")

div.οnclick=(e)=>{

    fun3("hi")

}

uniapp

<view

    bindtap="test"

    data-age="xx"

    data-sex="yy"

/>

function test(e){

  let {

    sex,age

  }=e.target.dataset

}

view.bindtap=test

模版解析

XML/JSON写法虚拟DOM对象
Vue

<div>

    <div>标题</div>

    <div>学校</div>

    <div>年级</div>

</div>

{
    "type": "div",

     ...
    "children": [
        {"children": "标题",...},
        {"children": "学校",...},
        {"children": "年级",...}
    ]
}

h('div',[

    h('div',"标题"),

    h('div',"学校"),

    h('div',"年级")

])

React

<div

    age={12}

    sex={'男'}

>
    <h3>标题</h3>
    <p>内容</p>
</div>

{
    type:'div',

    ...
    props:{
        age:12,sex:'男',
        children:[
            {type:'h3',props:{children:'标题'},...},
            {type:'p',props:{children:'内容'},...}
        ]
    }

createElement(

    'div', { age: 12, sex: '男' },
    [
      createElement('h3', null, '标题'),

      createElement('p', null, '内容')
    ]
)

样式处理

vue

scope模块化

css_class=>css_class[data-v-xxx]

react

module模块化

css_class=>_css_class_xxx_xx

组合式函数

React

普通函数fn中,能调用useState()

组件函数中:能调用useState()、fn()

事件处理程序、定时器、异步操作中,无法使用函数API和组合式函数

 Vue

普通函数fn中,能调用ref()

setup函数中,能调用ref()、fn()

性能优化

  • 懒加载路由组件
  • diff算法
  • 分页请求数据
  • TreeShaking第三方库
  • 防抖和节流减少事件触发频率
  • 压缩打包的代码
  • 缓存计算属性
  • keep-alive缓存Vue的路由组件实例

Vue的父组件重新渲染

  • props不变,子组件不重新渲染
  • props改变,子组件重新渲染

React的父组件重新渲染

  • 不用memo包裹子组件,子组件重新渲染
  • 用memo包裹子组件,isEqual=false,子组件重新渲染
  • 用memo包裹子组件,isEqual=true,子组件不重新渲染

memo(

    Child,

    (oldProps,newProps):boolean=>isEqual

)

数据注入

祖先组件后代组件
Vueprovide(key,value)const value=inject(key)
React

let Context=createContext(null)
return (
    <Context.Provider

      value={data}

    >
      ...
    </Context.Provider>
)

let data=useContext(Context)

特殊标签

  • Vue:组件标签、router-view、slot、template
  • React:组件标签、Outlet、Fragment,<></>

Vue的响应式

定义

ref定义

let age=ref(0)
age.value++          //触发set派发更新
age=2                //age变为普通数字,不再具有响应式

//组件mount时,age.value指向div实例
<div ref='age'></div>   

//组件mount时,items.value是一个数组,数组中有list.length个li实例 
<li v-for="item in list" ref="items"></li>

reactive定义

let info=reactive({
    age:12,
    simpleInfo:{
        age:14
    }
})
info.age=22             //响应式对象修改属性值、添加属性、移除属性,触发set派发更新
info={age:22}           //info变为普通对象,不再具有响应式

//age不具有响应式
//simpleInfo不具有响应式
//simpleInfo的属性仍具有响应式
let {age,simpleInfo}=info  
  • const name=computed(()=>{}):name是ref对象
  • pinia仓库数据:具有响应式
  • router和route上的数据:具有响应式 

watch监听

定义

watch([age,sex],(newVal,oldVal)=>{
    ....        
},{immediate:true,deep:true})

immediate:true ===> setup中watch监听就会执行一次

依赖收集

响应式数据age、sex收集了watch的回调函数

派发更新 

  • 修改响应式数据age、sex
  • 把age、sex收集的watch回调,放入异步队列
  • 运行异步队列,调用watch回调

数据源

watch(数据源,回调函数,配置项)

  •  ref对象
  • 响应式对象:默认开启深度监听
  • getter函数:深度监听需要手动开启;函数的返回值可以是响应式对象或响应式对象属性

页面渲染函数

依赖收集

  • 页面中,使用到了响应式数据age、sex
  • 页面渲染时,读取响应式数据age、sex的值
  • 响应式数据sex、age收集到渲染函数

派发更新

  • 修改响应式数据age、sex
  • 把age、sex收集的渲染函数,放入异步队列
  • 运行异步队列,调用渲染函数,重新渲染页面

注意点

  • 页面渲染时,会读取需要的响应式数据和非响应式数据
  • 页面渲染,组件为单位;父组件重新渲染&&props改变==>子组件也会重新渲染 
  • 异步队列中,只会执行一次页面渲染函数
  • 渲染函数==beforeUpdate+页面渲染+updated

d2c5838f3504416fb81038be8f6817ca.jpeg

c5c524b5ada74b9d8f73a6165a7c239c.jpeg

生命周期

配置项出现时间

eldata-methodprops-$attrs$ref
beforeCreated

已接收props

已接收attrs

created

已完成数据响应式

beforeMount

已编译模版

已创建虚拟DOM

mounted

已确定挂载点

已替换模版的{{xx}}占位符

已创建真实DOM

已挂载组件

父子组件生命周期执行顺序

挂载阶段

(父beforeCreated、父created) || (父setup)、父beforeMounted、

(子beforeCreated、子created) || (父setup)、子beforeMounted、子mouted、

父mounted

更新阶段

父beforeUpdate、

子beforeUpdate、子updated、

父updated

解绑阶段

父beforeCreated、

子beforeCreated、子destoryed、

父destoryed

计算属性

定义计算属性

const name=computed(()=>{
    return first_name+second_name
})

回调中响应式数据的处理

  • 修改响应式数据first_name、second_name,同步把计算属性name标记为"脏"

name的处理

  1. 读取响应式数据name
  2. 如果标记不是"脏",返回缓存的值
  3. 如果标记是"脏",同步运行计算属性回调,返回最新的值

可写计算属性

const name = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [first_name.value, last_name.value] = newValue.split(' ')
  }
})

React的响应式

四种数据

  • useState数据
  • useRef数据
  • 函数组件中的非响应式数据
  • 函数组件外的非响应式数据

useState

手写

let init=true
let cacheValue
function useState(initialValue){

    //数据初始化
    if(init){
        if(initialValue typeOf "function"){
            cacheValue=initialValue()    
        }else{
            cacheValue=initialValue
        }
        init=false
    }
    
    //更新函数
    function updateValue(newVal){
        if(newVal typeOf "function"){
            cacheValue=newVal(cacheValue)
        }else{
            cacheValue=newVal
        }

        //把组件函数放入异步队列
        setTimeout(组件函数,0)
    }

    //返回数据
    return [cacheValue,updateValue]
}

useState是浅层次的 

使用

useState初始化数据
let {age,setAge}=useState(initialValue)
let {age,setAge}=useState(()=>{return initialValue})

useState更新数据
setAge(age+1)
setAge((age)=>age+1)

useEffect

挂载阶段

  1. 组件函数
  2. DOM元素完成渲染并挂载
  3. useRef指向对应DOM元素
  4. useEffect回调

更新阶段

  1. 组件函数
  2. DOM元素完成渲染并挂载
  3. useRef指向对应DOM元素
  4. useEffect副作用
  5. useEffect回调

解绑阶段

  1. useEffect副作用

手写

let init=true
let cleanUp
let oldArr
function useEffect(setUp,newArr){
    
    //初始化
    if(init){
         //异步调用监听函数,收集副作用函数和监听数组
        setTimeout(()=>{  
            cleanUp=setUp()
            oldArr=newArr
            init=false
        },0)
        return
    }
    
    //把副作用函数放入队列    
    setTimeout(cleanUp,0)
    
    if(oldArr==undefined){
        //没写监听数组,直接把监听函数放入异步队列
        setTimeout(()=>{
                
            //调用监听函数,收集新的副作用函数
            cleanUp=setUp()
        },0)

    }else{
        for(let i=0;i<oldArr.length;i++){
            if(oldArr[i]!=newArr[i]){

                //监听的数据被修改,把监听函数放入异步队列
                setTimeout(()=>{
                
                    //调用监听函数,收集新的副作用函数
                    cleanUp=setUp()
                },0)
            }
        }
    }
    
    //更新监听数组
    oldArr=newArr
}

useEffect的监听是浅层次的

使用

useEffect(function setUp() {
    console.log(age,name)
    return function cleanUp() => {
        console.log(age,name)
    };
}, [age, name]);

组件函数初次运行

  • 运行useEffect的setUp函数,接收cleanUp函数

监听数据更新 || 没有配置数组

  1. 运行收集的cleanUp
  2. 运行setUp,接收新的cleanUp

组件卸载

  • 运行cleanUp

useRef

手写

let cacheValue={current:undefined}
let init=true
function useRef(initialValue){
    if(init){
        cacheValue.current=initialValue
        init=false
    }
    return cacheValue
}

使用

const num=useRef(0)
num.current++        //定义缓存数据

const btnRef=useRef(0)
btnRef.current++        //组件渲染前,btnRef.current依旧是数字
<button ref={btnRef}/>   //组件渲染时,btnRef.current会指向button实例


const btnRef=[useRef(),useRef(),useRef()]
<button ref={btnRef[0]}/>   //组件渲染时,btnRef[0].current会指向button1实例
<button ref={btnRef[1]}/>   //组件渲染时,btnRef[1].current会指向button2实例
<button ref={btnRef[2]}/>   //组件渲染时,btnRef[2].current会指向button3实例

 useCallback

手写

let init=true
let oldArr
let cacheFun
function useCallback(newFun,newArr){
    if(init){
        cacheFun=newFun
        oldArr=newArr
        init=false
    }
    
    //没写监听数组||监听数据改变,cacheFun指向新函数
    if(oldArr==undefined){
        cacheFun=newFun
    }else{
        for(let i=0;i<oldArr.length;i++){
            if(oldArr[i]!=newArr[i]){
                cacheFun=newFun
                break
            }
        }
    }
    
    return cacheFun
}

使用

  • const fun=useCallback(()=>{},[age,sex])
  • 监听数组的值没变化:fun的指向不变
  • 没写监听数组||监听数组的值变化:fun的指向改变

useMemo

手写

let cacheValue
let oldArr
let init=true
function useMemo(getValue,newArr){
    if(init){
        oldArr=newArr
        cacheValue=getValue()
    }
    
    if(newArr==undifined){
        cacheValue=getValue()
    }else{
        for(let i=0;i<oldArr.length;i++){
            if(oldArr[i]!=newArr[i]){
                cacheValue=getValue()
                break
            }
        }
    }
    
    return cacheValue
}

使用

  • const num=useMemo(()=>{},[age,sex])
  • 监听数组的值没变化,使用缓存数据
  • 没写监听数组||监听数组的值变化,重新计算新的值

useId

let cache
let init=true
function useId(){
    if(init){
        init=false
        //num∈[0,1,2,3.....]
        cache=`:r${num}:`
    }
    return cache
}

虚拟DOM

elementType

  • class xxx:类组件
  • div:普通标签
  • function xxx:函数组件

memoizeState

  • 类组件存放:state数据
  • 函数组件存放:useXxx的缓存数据
  • 普通标签存放:null

memoizedProps、pendingProps

  • 类组件存放:组件标签属性
  • 函数组件存放:组件标签属性
  • 普通标签存放:普通标签属性,children子标签

Other

  • child:首个子标签
  • sibling:下一个兄弟标签
  • return:父标签
  • ref:指向标签实例的ref属性

父子传值

概述

  • React的props <=> Vue的props+slot+attrs+style+class
  • props传递的数据不建议修改/不能修改
  • props对象的作用域<=>父组件的作用域

Vue广义的props

  • props:主动接收的组件标签上的属性
  • attr:未主动接收的组件标签上的属性
  • class、style:样式属性;分配给子组件的独生子标签
  • slot:组件内的children

样式属性处理

  • 路由组件标签直接丢弃children,样式属性放到attr上
  • 建议组件标签书写的样式:position、width、height;
  • 建议独生子标签书写的样式:display、text-align、其他

React的props

  • 组件标签上的属性
  • 组件内的children

父组件传递fn函数

  1. 父组件定义传递函数fn,fn的作用域是父组件的作用域
  2. 子组件调用props.fn,并传递参数,可以实现子向父传值

父组件传递空对象obj

  1. 父组件传递空对象obj
  2. 子组件向父组件上放入fn函数,即obj.fn=fn
  3. fn由子组件定义,fn的作用域是子组件的作用域
  4. 父组件可以调用obj.fn,运行子组件定义的函数fn

Vue插槽

  • 父组件提供插槽内容,子组件提供插槽出口
  • 模版内容和插槽出口依靠插槽名称对应起来
  • 插槽内容的变量作用域是父组件的变量作用域
  • 子组件能向父组件提供变量,父组件能接收变量并使用

Child组件

<div>
  <header>
    //name属性确定插槽名称是header;content属性是向父组件传递的值
    <slot name="header" content="headercontent"><slot>  
  </header>
  <main>
    //没有name属性,是默认插槽;content属性是向父组件传递的值
    <slot content="maincontent"><slot>                
  </main>
  <footer>
    //name属性确定插槽名称是footer,content属性是向父组件传递的值
    <slot name="footer" content="footercontent"><slot>
  </footer>
</div>

Parent组件

<div>
  <Child>

    //v-slot:header 简写 #header;接收子组件的content属性
    <template v-slot:header="{content}">    
      header-{{content}}
    </template>

    //如果不用接收子组件传递的值,默认插槽可以不用template包裹
    <template v-slot:default="{content}">   
      main-{{content}}
    </template>

    //接收子组件的content属性
    <template v-slot:footer="{content}">
      footer-{{content}}
    </template>
  </Child>
</div>

React插槽

JSX解析组件

<Child age={12},sex={'男'}>
    <Fragment key={'插槽111'}>
        <h3>插槽111</h3>
    </Fragment>
    <Fragment key={'插槽222'}>
        <h3>插槽222</h3>
    </Fragment>
    <Fragment key={'插槽333'}>
        <h3>插槽333</h3>
    </Fragment>
</Child>
====>
{
    type:'Child',
    props:{          //这个props将传给Child组件函数
        age:12,
        sex:'男',
        children:[
            {type:'Fragement',props:{key:'插槽111',children:[...]}},
            {type:'Fragement',props:{key:'插槽222',children:[...]}},
            {type:'Fragement',props:{key:'插槽333',children:[...]}}
        ]
    }
}

App.tsx

import { Fragment } from 'react'
import Child from '@/components/Child'
function App() {
  return (
    <>
      <h3>父组件</h3>
      <Child>
        <Fragment key={'插槽111'}>
          <h3>插槽111</h3>
        </Fragment>
        <Fragment key={'插槽222'}>
          <h3>插槽222</h3>
        </Fragment>
        <Fragment key={'插槽333'}>
          <h3>插槽333</h3>
        </Fragment>
      </Child>
      <h3>父组件</h3>
    </>
  )  
}
export default App

Child.tsx

import React from 'react';

export default function(props) {
    const {children}=props
    return (
      <>
        <h3>子组件</h3>
        {children.find((value)=>{
            return value.key=="插槽333"
        })}
        {children.find((value)=>{
            return value.key=="插槽222"
        })}
        {children.find((value)=>{
            return value.key=="插槽111"
        })}
        <h3>子组件</h3>
      </>
    );
}

Vue的更强封装

全局API

app.use

  • app.use(obj,args)会调用参数obj的install(app,args)方法
  • install的参数app:app实例
  • install的参数args:app.use方法的args
app.use=function(obj,args){
    ...
    obj.install(app,args)
    ...
}

strengthObj={
    install:function(app,args){
        .....
    }
}
app=createApp({})
app.use(strengthObj,args)

Other

  • app.component
  • app.directive
  • app.provide
  • app.mount
  • app. unmount

指令

自定义指令

标签的生命周期

<p v-my-directive:参数名.修饰符1.修饰符2="属性名"></p>
let 属性名="属性值"
const vMyDirective={
  created(el,binding,vnode,prevVnode){},
  beforeMount(el,binding,vnode,prevVnode){},
  mounted(el,binding,vnode,prevVnode){},
  beforeUpdate(el,binding,vnode,prevVnode){},
  updated(el,binding,vnode,prevVnode){},
  beforeUnmount(el,binding,vnode,prevVnode){},
  unmounted(el,binding,vnode,prevVnode){},
}


el:p标签指向的真实DOM;created也能访问p标签指向的真实DOM
binding:{
    arg:"参数名",
    modifiers:{修饰符1:true,修饰符2:true},
    value:"属性值",                        //value=属性名="属性名"
    oldValue:undefined
    ...
}

内置指令

v-for、v-if、v-show

  • vue3.2.47中,同一标签上优先级,v-if>v-for>v-show
  • v-if:销毁真实DOM,保留虚拟DOM
  • v-show:隐藏真实DOM

v-for和v-if在同一标签上

  • v-if先执行,导致无法使用v-for的数据
  • v-if=false,标签销毁,v-for不会再执行
  • v-if=true,v-for创建多个标签,但是每个标签上的v-if都等于true

动态class

动画

<video class="video_screen" :class="{ fold: fold }" controls src="#"></video>

const fold = ref(false)
const openComment = () => {
  fold.value = true
}
const closeCommentDrawer = () => {
  fold.value = false
}

.video_screen {
  height: 100%;
  width: 100%;
  border-radius: 10px;
  transition-duration:2s;
  &.fold {
    width: calc(100% - 300px);
    transition-duration:2s;
  }
}

暗黑模式+背景图切换

const {dark} =ref(dark)

<div :class="{dark:dark}">
  ......
  <el-switch v-model="dark" />
  ......
</div>

<div class="header-banner" 
    :style="{background:dark?
        'url(src/assets/moon.jpg)':
        'url(src/assets/sun.jpg)'}"
    >
</div>

.dark{
    background-color: black;
}

路由

何为路由

  • v-show、v-if:实现页面切换最简单的方式
  • 路由:在v-show、v-if之上,进行了更好的封装
<script setup>
import { ref, computed } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import NotFound from './NotFound.vue'
const routes = {
  '/': Home,
  '/about': About
}
const currentPath = ref(window.location.hash)
window.addEventListener('hashchange', () => {
  currentPath.value = window.location.hash
})
const currentView = computed(() => {
  return routes[currentPath.value.slice(1) || '/'] || NotFound
})
</script>
<template>
  <a href="#/">Home</a> |
  <a href="#/about">About</a> |
  <a href="#/non-existent-path">Broken Link</a>
  <component :is="currentView" />
</template>

路由使用

概述

  • 创建routes路由表,路由组件放入路由表
  • 创建router对象,routes放入router
  • router放入根组件
  • 设置路由出口

Vue使用路由

创建routes路由表

routes路由表添加组件

const routes=[
    {path:'/xx',component:xxx},
    {
      path:'/xx',
      component:xxx,
      children:[

        //path:''表示此子路由是默认子路由
        {path:'',component:xxx},

        //path:'yy'<=>path:'/xx/yy'
        {path:'yy',component:xxx},
      ]
    },
    {

        path:'/:pathMatch(.*)*',

        redirect:'/404'

    }
]

创建router对象

确定router类型
routes放入router

const router=createRouter({
    routes,
    history:xxx
})

把router注册到app

设置一级出口

import { RouterView } from 'vue-router';

import { createApp } from 'vue';

import router from './router';

const app=createApp(RouterView)

app.use(router)

app.mount('#app')

设置二级出口<RouterView />

React使用路由

创建routes路由表

routes路由表添加组件

let routes=[
    {
        path:'/xx',
        Component:xxx,
        children:[

            //path:''表示此子路由是默认子路由      
            {path:'',Component:xxx},

            //path:'yy'<=>path:'/xx/yy'
            {path:'yy',Component:xxx},
          ]
    },
    {

      path:'/xx',

      element:<xxx />,

      children:[...]

    },
    {

      path:'/*',

      element:<Navigate to='/xx' />

    }
]

创建router对象

确定router类型
routes放入router

let router=createBrowserRouter(routes)

router放入根组件

设置一级出口

ReactDOM

  .createRoot(document

  .getElementById("root"))

  .render(
    <RouterProvider router={router} />
);

设置二级出口{Outlet}
ReactDOM.createRoot(document.getElementById("root")).render(
    //确定router类型、router注入根组件
    <BrowserRouter>    
         //确定路由出口、路由组件放入route、route放入router 
        <Routes>       
            <Route path="/home" element={<Home/>}></Route>
            <Route path="/login" element={<Login/>}></Route>
            <Route path="/*" element={<Navigate to="/home" />}></Route>
        </Routes>
    </BrowserRouter>
);
let element=useRoutes([
    {
        path:'/xx',
        Component:xxx,
        children:[
            {path:'',Component:xxx},     //path:''表示此子路由是默认子路由      
            {path:'yy',Component:xxx},   //path:'yy'<==>path:'/xx/yy'
          ]
    },
    {path:'/xx',element:<xxx />,children:[...]},
    {path:'/*',element:<Navigate to='/xx' />},
])

ReactDOM.createRoot(document.getElementById("root")).render(
    <BrowserRouter>     
        {element}
    </BrowserRouter>
);

简单对比

vuereact
标签导航<router-link to="/" /><Link to='/sing' />
编程导航

const $router=useRouter()

$router.push('')

const navigate=useNavigate()

navigate('')

出口<router-view />

<RouterProvider router={router}/>

{Outlet}

传参方式

params

query

params

search(类似query)

state

标签传参

<router-link 

    to='/sing/a/b' 

/>

<router-link

    to='/dance?a1=a&a2=b'

/>

 <Link

    to='/sing/a/b'

/>
 <Link

    to='/dance?a1=a&a2=b'

/>
 <Link

    to='/rap'

    state={{a1:'a',a2:'b'}}

/>

<router-link :to="{

        path/name:"...",

        query:{a1:'',a2:''}

}" />

<router-link :to="{

        name:'',

        params:{a1:'',a2:''}

}" />

编程传参

$router.push{

        path:''(或name:''),

        query:{a1:'',a2:''}

}

$router.push{

        name:'',

        params:{a1:'',a2:''}

}

navigate('/sing/a/b')

navigate('/dance?a1=a&a2=b')

navigate('/rap',{state:{a1:'a',a2:'b'}})

接参方式

$route.params

$route.query

let {name,song}=useParams()

let [search,setSeach]=useSearchParams()

let name=search.get('name')

let song=search.get('song')

let {name,song}=useLocation().state

懒加载

{

  path:'xxx',

  component()=>import('xxx')

}

{

    path:'xxx',

    async lazy(){

        let Home = await import("xxx")

        return {

            Component: Home.default

        };

    }

}

xxx.tsx文件

const Home=lazy(()=>import('xxx'))

{

    path:'xxx',

    element:<Home />

}

xxx.tsx文件

{

    path:'xxx',

   .Component:lazy(()=>import('xxx'))

}

匹配顺序

从前向后

通配路由建议放到最后

通配路由放到最后和最前效果相同

从前向后

通配路由建议放到最后

动态路由addRoute、addRoutes

路由鉴权

Vue 内置鉴权

全局前置
router.beforeEach((to,from,next)=>{...})

全局后置
router.afterEach((to,from)=>{...})

独享前置
beforeEnter((to,from,next)=>{...})

next()
next(false)
next('xxx')
next({path:'xxx',query:'xxx'})

 React 高阶组件(Hoc)实现鉴权

import React from 'react';
import { Navigate } from "react-router"

const withAuth = (WrappedComponent) => {
  return function(props) {
    const isLoggedIn = checkUserAuth(); // 检查用户是否已登录,可根据实际需求实现
    if (!isLoggedIn) return <Navigate to="/home" />;
    return <WrappedComponent {...props} />;
  };
};

// 使用withAuth高阶组件包裹需要进行鉴权的路由组件
const AuthHome = withAuth(Home);

多级路由

  1. 一级路由占有部分可支配页面,其余分给二级路由。建议写法:/xxx
  2. 二级路由占有部分可支配页面,其余分给三级路由。建议写法:/xxx/xxx
  3. 三级路由占有部分可支配页面,其余分给四级路由。建议写法:/xxx/xxx/xxx
  4. 一级路由的可支配页面是整个浏览器的页面
  5. /yy也可以是/xx的子路由,但是这种写法可读性极差,建议把/yy写成/xx/yy。
//标准写法
[
    {
        //通过/xx访问
        path:"/xx"
        children:[
            //通过/xx/yy访问    
            {path:"/xx/yy"},  
            //通过/xx/zz访问
            {path:"/xx/zz"}
        ]
    }
]

//简略写法
[
    {
        path:"/xx"
        children:[
            {path:"yy"},  //等价于{path:"/xx/yy"}
            {path:"zz"},  //等价于{path:"/xx/zz"}
        ]
    }
]

//不建议写法
[
    {
        path:"/xx"
        children:[
            //通过/yy访问
            {path:"/yy"},  
            //通过/zz访问
            {path:"/zz"},
        ]
    }
]

目录结构

vue

react

src

        assets

        utils

        api

                index.ts

                request.ts

        store

                index.ts

                store1.ts

                store2.ts

        router

                index.ts

                routes.ts  

        pages
                images
                pageA
                        subPages
                        ComA.vue
                        ComB.vue
                        ComC.vue
                        index.vue
                PageB.vue
                PageC.vue
        components
                ComA.vue
                ComB.vue
                ComC.vue

src

        assets

        utils

                request.ts

                store.ts

        router

                index.ts

                routes.ts  

        pages
                pageA
                        subPages
                        index.jsx(子组件写在index里)
                        index.scss

                        services.ts

                        model.ts

                PageB
                        index.jsx(子组件写在index里)
                        index.scss

                        services.ts

                        model.ts

        components

                ComA
                        index.jsx
                        index.scss
                ComB

                        index.jsx
                        index.scss

一级路由可以提取到src目录下

react子组件函数、父组件函数,能写在同一文件下

 路由模式

  1. http://ip:port/pathname/?key=value#/123
  2. http://ip:port是域名
  3. pathname是前端向后端请求的资源,
  4. ?key=value携带query参数信息
  5. #携带其他信息
hash模式

前端路由信息写在#后面

前端路由信息更改,不会向后端请求新的资源

history模式

前端路由信息直接写在域名后面

前端路由信息更改,会向后端请求新的资源

这时后端需要加工处理

内存模式

不再使用浏览器导航栏的url

框架维护一个内置的路由表

仓库

自定义仓库

创建仓库

//store.js

export const miniStore={
    age:10,
    sex:"男",
    name:"Tom"
}

export const store={
    state:{……},
    getters:{……},
    actions:{……},
    reducers:{……},
    modules:{
        module1:{},
        module2:{},
        ……
    }
}

使用仓库

//组件.js

import {miniStore,store} from 'store.js'

实现响应式

  •  Vue,用proxy或defineProperty处理仓库数据,以依赖收集和派发更新的方式更新组件
  • React,给仓库数据注册event监听事件,数据变化就全局重新渲染

数据注入

在根组件上,把仓库数据传递进去

Redux

概述

20215e27db6a40ccaf83db2fdf580125.jpeg

function todoApp(state=initialState,action){}

//初始化仓库,第一次调用todoApp
let action=null
let cacheState=undefined
cacheState=todoApp(cacheState,action)

//更新仓库数据,第二次及以上调用todoApp
action=接收的action
cacheState=todoApp(cacheState,action)

action的作用

  • reducer是纯函数,负责直接修改数据
  • action可以是副作用函数,负责数据修改前的逻辑

同步使用&对象

  1. const actionSynch={type:"",payload:{...}}
  2. dispatch(actionSynch)
  3. 同步修改仓库数据

异步使用&函数

  1. function actionAsync(dispatch){...}
  2. dispatch(actionAsync)
  3. 异步修改仓库数据

原生创建仓库

-------------大仓库管理一个小仓库-----------------
function todoApp(state = initialState, action) {
  switch (action.type) {
    case xxx:
        ...
        return xxx
    case xxx:
        ...
        return xxx
    default:
        return state
  }
}

let store = createStore(todoApp,applyMiddleware(thunk))

-------------大仓库管理多个小仓库-----------------
function todoHome(state=initialState1,action) {
  switch (action.type) {
    case xxx:
        ...
        return xxx
    case xxx:
        ...
        return xxx
    default:
        return state
  }
}

function todoLogin(state=initialState2,action) {
  switch (action.type) {
    case xxx:
        ...
        return xxx
    case xxx:
        ...
        return xxx
    default:
        return state
  }
}

function todoApp(state={},action) {
  return {
      todoHome:todoHome(state.todoHome,action),
      todoLogin:todoLogin(state.todoLogin,action)
  }
}

//redux需要注册中间件redux-thunk,才支持异步action
const store = createStore(todoApp,applyMiddleware(thunk))

store.getStore():获取仓库数据
store.dispatch(action):更新仓库数据
const unsubscribe=store.subscribe(func):仓库数据更新,运行监听函数func
unsubscribe:取消函数func的监听功能

原生使用redux仓库

  • react组件中注册redux的subscribe监听
  • react组件更新redux中的数据
  • redux中的数据改变,触发subscribe监听函数
  • subscribe监听函数调用useState("")
  • react组件重新渲染

@reduxjs/toolkit创建仓库

import { createSlice,configureStore } from '@reduxjs/toolkit'
//@reduxjs/toolkit,默认注册了redux-thunk

--------------------草稿----------------------
const counterSlice = createSlice({
  name: 'counter',         //name属性对应action对象的前缀
  initialState: 0
  reducers: {
    increment(state){
      state+= 1
    },
    incrementByAmount(state, action){
      state+= action.payload
    }
  }
})

const nameSlice = createSlice({
  name: 'name',
  initialState: 'Tom'
})


-----------------生成actions----------------------
export const { increment,incrementByAmount,xxx } = counterSlice.actions

function xxx(payload){
    return {
        type:userSlice.name+"/"+xxx.name,
        payload:payload
    }
}

-----------------生成reducer----------------------
const counterReducer=counterSlice.reducer
const nameReducer=nameSlice.reducer

-----------------生成大仓库----------------------
const store=configureStore({
  reducer: {
    //counter、name属性是useSelector获取属性时用的到
    counter: counterReducer,  
    name: nameReducer
  },
  middleware(getDefaultMiddleware){
      return getDefaultMiddleware({
          serializableCheck:false
       })
  }
})

react-redux使用仓库

//react-redux的Provider组件,把仓库store注入最外层组件App
import { useSelector, useDispatch, Provider } from 'react-redux'

<Provider store={store}>
    <App>...</App>
</Provider>

const dispatch=useDispatch(xxx(payload))
const value=useSelector((state)=>{  //state={counter:0,name:'Tom'}
    return state.xxx.yyy...
})

Pinia

创建仓库

function defineStore(仓库名,配置项){
    return function(大仓库=app?.pinia)
}

--------------小仓库的创建----------------------
const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    }
  }
})
--------------大仓库的创建----------------------
const pinia=createPinia()


-----全局注册&&使用小仓库&&小仓库注入大仓库-------
app.use(pinia)         
const counterStore=useCounterStore()


--------使用小仓库&&小仓库注入大仓库-------
const counterStore=useCounterStore(pinia)             

使用仓库数据

const counter = useCounterStore()
counter.count++
counter.$patch({ count: counter.count + 1 })
counter.increment()

仓库数据解构

  1. xxxStore.yyy=zzz
  2. {yyy}=storeToRefs(xxxStore)&&yyy=zzz
  3. {yyy}=xxxStore&&yyy=zzz 
  4. 1、2、3都可以修改仓库属性;1、2是响应式的,3不是

Umi

 目录结构

├── package.json
├── .umirc.ts
├── .env
├── dist
├── mock                 //约定mock文件夹下,所有文件都当成mock文件解析
├── public
└── src
    ├── .umi
    ├── layouts/index.tsx
    ├── pages
        ├── index.less
        ├── index.tsx
        └── document.ejs  //约定默认模板
    ├── app.ts            //约定运行时配置文件
    └── global.less       //约定全局样式文件

样式

  • umi3默认支持less
  • import style from 'xxx.less':识别出CSS Modules文件
  • import 'xxx.less':识别成普通css文件

图片

  • <img src={require("./xxx.png")} />:相对路径引入
  • <img src={require("@/xxx.png")} />:使用@代指src
  • import logo from './logo.svg';<img src={logo} />:url式引入
  • import {ReactComponent as Logo} from './logo.svg';<Logo />:组件式引入
  • .logo{ background : url(./xxx.png) }:相对路径引入
  • .logo{ background : url(~@/xxx.png)}:使用~@代指src

前端路由

routes配置

.umirc.ts文件

export default defineConfig({

    ....

    routes:[

        {path:/xx,component:"@/pages/xx" exact:true}

        {

            path:/yy,component:"@/pages/yy",exact:false

            routes:[

                {path:/yy/zz,component:"@/pages/yy/pages/zz",exact:true},

                {path:/yy/gg,component:"@/pages/yy/pages/gg",exact:true} 

             ]

        }

    ]

    ....

})

编程式导航

history.push('/url?k1=v1&k2=v2')

history.push({

  pathname: 'url',

  query: {k1: v1,k2:v2},

})

组件式导航

<Link to="/url"></Link>

history对象

获取方式一:props.history

获取方式二:import { history } from "umi"

history跳转方法:push、replace、go、goBack、goForward

history属性:

      location对象:包含pathname、search、query、hash、state属性

      action:当前路由的action,PUSH/REPLACE/POP三种

      length:history栈的实体数

props对象

props

  children

  history对象

  location对象

  route当前路由信息

  routes所有路由信息

  match

  staticContext

路由懒加载dynamic

dynamic({
  loader:()=>import("/xxx"), //按需加载的模块
  loading:loadingComponent   //加载模块时,显示的组件
  delay:2000                 //模块2s后开始加载
  timeout:5000,              //模块开始加载后,5s内没能完成加载,则加载失败
  error:errorComponent       //加载模块失败后,显示的组件
})

网路请求

request请求API

request('url', {
  method: "xxx",
  params: {...},
  data:{...},
  headers:{...},
  timeout:5000,
  credentials:"omit/include"
  mode:"cors/no-cors"
})

request全局配置

//采用@umijs/plugin-request
//配置文件src/app.tsx

import { RequestConfig } from 'umi';

export const request: RequestConfig = {
  timeout: 5000,             //超时时间
  errorConfig: {},
  middlewares: [],
  requestInterceptors: [],   //请求拦截器
  responseInterceptors: [],  //响应拦截器
};

mockjs拦截请求和模拟数据

import mockjs from 'mockjs' //可以使用mockjs模拟数据

export default {
  //请求方法,请求url,响应数据
  'GET /api/users': { users: [1, 2] },

  //请求方法默认是GET
  '/api/users/1': { id: 1 },

  //请求方法,请求url,响应函数(同express)
  'POST /api/users/create': (req, res) => {
    res.end('ok');
  },
}

dva仓库

小仓库(model)位置

  • 约定src/models下,xxx.ts文件
  • 约定src/pages下,models/xxx.ts文件
  • 约定src/pages下,model.ts 文件

仓库属性

  • namespace:仓库名称;唯一标识
  • state:仓库数据
  • reducers:同步更新仓库数据
  • effects:运行异步操作;
  • subscriptions:dva仓库注册时,自动调用subscriptions中的函数

仓库API

  • useSelector(state=>state):返回大仓库数据
  • const dispatch=useDispatch();dispatch({type:'小仓库namespace/方法名',...arg})
  • connect:绑定数据到组件
  • getDvaApp():获取dva实例
  • useStore():不知道什么用法

示例

import React from "react";
import {useSelector,useDispatch} from 'umi'

export default function PageLayout() {
    const list=useSelector(state=>state.list)
    const dispatch=useDispatch()
    function addAgeFn(){
        dispatch({type:"list/addAge"})          //需要加仓库名前缀
    }
    function asyncAddAgeFn(){
        dispatch({type:"list/asyncAddAge"})
    }
    return (
        <>
            <button onClick={addAgeFn}>同步增加list仓库年龄{list.age}</button>
            <button onClick={asyncAddAgeFn}>同步增加list仓库年龄{list.age}</button>
        </>
    );
}


export default {
    namespace: 'list',
    state: {age: 11},
    reducers: {
        addAge(state, action) {
            return {
                ...state,
                age: ++state.age
            }
        }
    },
    effects: {
        *asyncAddAge(_, { call, put }) {
            console.log("计时开始")
            yield call(delay, 1000)
            console.log("计时结束")
            yield put({ type: 'addAge' })       //不需要加仓库名前缀
        },
    },
    subscriptions: {
        subscription1({ dispatch, history }) {
            dispatch({ type: 'addAge' })        //不需要加仓库名前缀
        },
        subscription2({ dispatch, history }) {
            dispatch({ type: 'addAge' })        //不需要加仓库名前缀
        },
    }
        
}

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

useModel仓库

仓库创建

约定src/models下,xxx.ts文件

import { useState,useCallback } from "react"

export default function useAuthModel() {
    let [user,updateUser] = useState("")
    const setUser = (newUser) => {
        updateUser(newUser)
    }
    const clearUser = () => {
        updateUser("")
    }
    return {user,setUser,clearUser}
}

仓库的使用

import { useModel } from "umi";

export default function Home(props) {
  let useAuthModel=useModel(
    //仓库名称,即仓库文件名称
    'xxx',                                            
    //参数可选,默认返回xxx仓库所有数据;也可以主动设置仓库数据
    model=>({user:model.user,setUser:model.setUser})  
  )
  function showModel(){
    console.log(useAuthModel)
  }
  function changeModel(){
    useAuthModel.setUser("Home")
  }

  return (
    <>
      <button onClick={showModel}>showModel</button>
      <button onClick={changeModel}>changeModel</button>
    </>
  );
}

@umijs/plugin-initial-state

src/app.tsx

export async function getInitialState() {  //生产@@initialState数据
    return {name:"LiHua",age:32}
}


xxx组件

export default function xxx(){
    let initData=useModel("@@initialState") //使用@@initialState数据
    return (...)
}

src/app.ts默认方法

//修改 clientRender 参数
export function modifyClientRenderOpts(fn){}

//修改路由
export function patchRoutes({ routes }){}

//覆写 render
export function render(oldRender: Function){}

//在初始加载和路由切换时做一些事情
export function onRouteChange({ routes, matchedRoutes, location, action }){}

//根组件外包一个Provider;
//args包括routes、plugin、history 
export function rootContainer(LastRootContainer, args){}

.umirc.ts配置文件

initUmiConfig方法

initUmiConfig({
  mf: "development",
  ngBuild: true,
  routes: [
     {path: "/", component: "@/pages/layout", routes: []}
  ]
}

========>

{
  publicPath: './',
  base: '/',
  nodeModulesTransform: { type: 'none' },
  ignoreMomentLocale: true,
  history: { type: 'hash' },
  theme: {
    '@layout-body-background': '#f0f2f5',
    '@body-background': '#f0f2f5',
    '@layout-header-background': '#fff',
    '@layout-sider-background': '#001529',
    '@menu-dark-bg': '#001529',
    '@tooltip-color': '#333',
    '@tooltip-bg': '#fff',
    '@modal-header-bg': '#FAFAFA',
    '@tabs-horizontal-gutter': '16px',
    '@line-height-base': '1.5',
    '@checkbox-size': '14px',
    '@font-size-base': '12px',
    '@height-base': '28px',
    '@steps-title-line-height': '32px',
    '@form-label-width': '86px',
    '@form-item-margin-bottom': '20px',
    '@tabs-bar-margin': '0px',
    '@modal-border-radius': '12px',
    '@table-header-bg': '#f4f4f4',
    '@border-color-split': '#ECECEC',
    '@wait-icon-color': '#00000040',
    '@disabled-color': '#666666',
    '@disabled-bg': '#F7F8FA',
    '@select-multiple-item-disabled-color': '#666666',
    '@select-multiple-item-disabled-border-color': '#F7F8FA',
    '@root-entry-name': 'variable'
  },
  antd: { disableBabelPluginImport: true },
  dva: {
    skipModelValidate: true,
    disableModelsReExport: true,
    lazyLoad: true
  },
  dynamicImport: { loading: '@/components/pageLoading' },
  terserOptions: {},
  routes: [ { path: '/', component: '@/pages/layout', routes: [] } ],
  mfsu: {},
  webpack5: {}
}

默认别名项目

  • @:src 目录
  • @@:临时目录,通常是 src/.umi目录
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stealPigs-youth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值