React&Vue 系列:一篇说完组件的所有通信方式

React&Vue 系列:一篇说完组件的所有通信方式

背景:作为使用三年 react.js 的搬运工,目前正在积极学习 vue.js 语法,而在学习的过程中,总是喜欢和 react.js 进行对比,这里也不是说谁好谁坏,而是怀有「他有我也有,他没我还有」的思想去学习,去总结。

  • React18
  • Vue3

现在无论是 React 开发还是 Vue 开发,都有如下特点:

  1. 单页面SPA开发(「SSR」 除外)。
  2. 基于组件化开发。

基于上面的特点之一,理解组件通信,是至关重要的。

组件可以分成多种类型,比如说页面组件,功能组件等等。那么针对组件的通信也可大可小,但是总的类型分为三种:

  • 父传子(简称:T1)
  • 子传父(简称:T2)
  • 跨组件传递(简称:T3)

话不多说,正题开始。

下面的案例都是采用 ts 写法,如果想写 js 写法,只需要去掉类型即可。

React:组件通信

在前面的一篇中,梳理了对 React&Vue 系列:props ,

https://juejin.cn/post/7259298176524009533

正好可以充分利用 props。

T1: props 传递

子组件想要的数据(变量,方法),父组件就通过属性传递下去。当父组件中的状态发生了变化,父组件重新渲染,子组件也就会跟着渲染,保持同步(数据单向流)。

// Son.tsx
import type { FC } from "react";
interface Props {
  name: string;
}
const Son: FC<Props> = (props) => {
  const { name } = props;
  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
    </>
  );
};
export default Son;

// Father.tsx
import { useState } from "react";
import Son from "./Son";
const Father = () => {
  const [name, setName] = useState("copyer");
  return (
    <>
      <h2>我是父组件</h2>
      {/
* 传递 name 属性 */}
      <Son name={name}></
Son>
    </>
  );
};

export default Father;

T2:props 传递

利用 props 的回调函数,实现子传父。

实现思路:子组件调用父组件传递过来的 props(方法),以参数的形式传递给父组件,改变父组件状态。

// Son.tsx
import type { FC } from "react";

interface Props {
  name: string;
  setName?: (name: string) => void;
}

const Son: FC<Props> = (props) => {
  const { name, setName } = props;
  const btn = () => {
    // 调用父组件的方法,改变父组件的状态
    setName && setName("james");
  };
  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
      <button onClick={btn}>点击</button>
    </
>
  );
};
export default Son;
// Father.tsx
import { useState } from "react";
import Son from "./Son";
const Father = () => {
  const [name, setName] = useState("copyer");
  const changeName = (name: string) => {
    setName(name);
  };
  return (
    <div>
      <h2>我是父组件</h2>
      <Son name={name} setName={changeName}></
Son>
    </div>
  );
};

export default Father;

T2:ref 获取数据

利用 ref 操作 dom 方式,拿取子节点,强行获取数据。

实现思路:

  1. 父组件在使用子组件的时候,传递一个 ref 给子组件。
  2. 但是又由于 jsx 转化为虚拟 DOM 的过程中,ref 是一个保留属性,是不会传递到 props 中的,就需要使用 「forwardRef」 进行转化,拿取到 ref。
  3. 父组件就通过 ref 直接调用暴露(「useImperativeHandle」)出来的方法,拿取数据。
// Son.tsx
import type { Ref } from "react";
import { useImperativeHandle, forwardRef, useState } from "react";

interface Props {
  name: string;
}
type RefType = Ref<{ getData: () => string }>;

const Son = (props: Props, ref: RefType) => {
  const { name } = props;
  const [data, setDate] = useState("我是子组件中的数据");

  // 暴露方法供外部组件调用
  useImperativeHandle(ref, () => {
    return {
      getData: () => data,
    };
  });

  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
    </>
  );
};

/
/ 转化ref
const ForwardRefSon = forwardRef(Son);
export default ForwardRefSon;
// Father.tsx
import { useState, useRef } from "react";
import type { ElementRef } from "react";
import Son from "./Son";
const Father = () => {
  const [name, setName] = useState("copyer");
  const ref = useRef<ElementRef<typeof Son>>(null);

  const getSonData = () => {
    // 拿取子组件的数据
    const data = ref.current?.getData();
  };
  return (
    <div>
      <h2>我是父组件</h2>
      <Son ref={ref} name={name} /
>
      <button onClick={getSonData}>获取子组件数据</button>
    </
div>
  );
};

export default Father;

T3:context 分享数据

context 就类似一个上层的汇集地,提供了共享的属性和方法,供下层组件使用。最常用的使用场景:系统主题的变量等。当然 redux, react-router-dom 的内部实现都是采用的 context。

这里就提供函数组件的写法,类组件的写法就是在消耗的时候,写法不一样

// Father.tsx
import { useState, useRef, createContext } from "react";
import Son from "./Son";

interface ContextValue {
  name: string;
  changeName?: (value: string) => void;
}

// 定义一个 Context
export const FatherContext = createContext<ContextValue>({
  name: "默认的名称",
});

const Father = () => {
  const [name, setName] = useState("copyer");
  const changeName = (name: string) => {
    setName(name);
  };
  return (
    <div>
      <h2>我是父组件</h2>
      {/
* Context 提供者 */}
      <FatherContext.Provider value={{ name, changeName }}>
        <Son /
>
      </FatherContext.Provider>
    </
div>
  );
};

export default Father;
// Son.tsx
import type { FC } from "react";
import { useContext } from "react";
import { FatherContext } from "./Father";

const Son: FC = () => {
  // 消耗者(获取属性和方法)
  const context = useContext(FatherContext);
  const { name, changeName } = context;
  const btn = () => {
    changeName && changeName("james");
  };
  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
      <button onClick={btn}>点击</button>
    </
>
  );
};

export default Son;

T3: 状态管理库 @reduxjs/toolkit

「Redux Toolkit」 是官方新推荐的编写 Redux 逻辑的方法(可能官方也感觉以前 redux 学习成本高,写法复杂吧,猜测哈)。

可以自己看下:为什么 Redux Toolkit 是如今使用 Redux 的方式

https://redux.js.org/introduction/why-rtk-is-redux-today

虽然 redux 出现了新的语法,使用变得更加的简洁,但是其中的基础概念

https://redux.js.org/tutorials/essentials/part-1-overview-concepts

还是必须了解一下。

重点理解:

  • 单项数据流
  • redux 的三大核心概念:action | reducer | store
  • redux 的数据流(基于单项数据流)

具体使用。

「安装」

pnpm add @reduxjs/toolkit react-redux

「具体使用」

第一步:定义 slice,假设关于用户的名称(userSlice)

// userSlice.ts
import { createSlice } from "@reduxjs/toolkit";

interface IUser {
  name: string;
}
const initialState: IUser = {
  name: "copyer",
};

// 定义初始值和 reducer
const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    changeName: (state, actions) => {
      state.name = actions.payload.name;
    },
  },
});

// 暴露 actions
export const { changeName } = userSlice.actions;

// 导入注册reducer
export default userSlice.reducer;

第二步:注册所有的 reducer,并导出 store

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./slices/userSlice";

const store = configureStore({
  // 注册所有的 reducer
  reducer: {
    user: userReducer,
  },
});

// 这里的类型,为了后面两个自定义 hooks 做准备
export type RootState = ReturnType<typeof store.getState>;
export type Dispatch = typeof store.dispatch;

export default store;

第三步(可选):为了后面书写 ts 更加的方便,自定义两个 hooks,代替 react-redux 提供的 hooks

// hooks/useAppStore.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, Dispatch } from "@/store";

export const useAppDispatch: () => Dispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

第四步:为 React 提供 store

import { Provider as StoreProvider } from "react-redux";

const App = () => {
  return <StoreProvider store={store}>{/* 内部代码 */}</StoreProvider>;
};

第五步:组件中使用

// Son.tsx
import type { FC } from "react";
import { useAppDispatch, useAppSelector } from "@/hooks/useAppStore";
import { changeName } from "@/store/slices/userSlice";

const Son: FC = () => {
  const { name } = useAppSelector((state) => state.user);
  const dispatch = useAppDispatch();
  const btn = () => {
    dispatch(changeName({ name: "james" }));
  };
  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
      <button onClick={btn}>点击</button>
    </
>
  );
};

export default Son;

基本使用的大致流程就是这样的,相对于以前的 redux 写法(文件多)简便太多了,还是值得推荐的。

异步?看 额外知识:RTK 异步处理

「dva/core」 也是一种选择,基于 redux 的,写法也简便。不过有了 RTK,还是选择 RTK 吧。

T3: 状态管理库 mobx

mobx 是另外的一种状态管理库,也可以在其他框架中使用。其特点:

  • 简单、可扩展的状态管理工具
  • 函数响应式编程(利用 proxy)

可以先熟悉一下 额外知识:mobx 的知识要点

「安装」

pnpm add mobx mobx-react-lite

# mobx-react-lite 针对函数组件
# mobx-react 函数组件,类组件都可

「具体使用」

第一步:定义类,创建响应式对象

// store/User.ts
import { observable, action, computed, makeObservable } from "mobx";

class User {
  name: string;
  constructor() {
    this.name = "copyer";
    makeObservable(this, {
      name: observable,
      changeName: action,
      upperName: computed,
    });
  }
  changeName(value: string) {
    this.name = value;
  }
  get upperName() {
    return this.name.toUpperCase();
  }
}

export default User;

第二步:收集类,创建一个根类,供应用使用。也创建一个 Context,一个自定义 hook,使用 useContext

// store/index.ts
import { createContext, useContext } from "react";
import UserStore from "./User";

// 创建一个根类
class RootStore {
  userStore: UserStore;
  constructor() {
    // 集中类
    this.userStore = new UserStore();
  }
}
const rootStore = new RootStore();

// 创建 Context
const RootStoreContext = createContext<RootStore>({} as RootStore);

// 创建一个自定义 hook, 用来消费 Context
const useStore = () => {
  return useContext(RootStoreContext);
};

export { rootStore, useStore };
export default RootStoreContext;

第三步:在系统应用中注册(提供,provider)

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import RootStoreContext, { rootStore } from "./mobxStore";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RootStoreContext.Provider value={rootStore}>
      <App />
    </RootStoreContext.Provider>
  </
React.StrictMode>
);

第四步:在组件中消耗(consumer)

// App.tsx
import { observer } from "mobx-react-lite";
import { useStore } from "../mobxStore";

const AppInner = () => {
  const userStore = useStore().userStore;
  const asynClick = () => {
    userStore.changeName("james");
  };
  return (
    <div>
      <span>{userStore.name}</span>
      <span>{userStore.upperName}</
span>
      <button onClick={asynClick}>同步</button>
    </
div>
  );
};

// 为了解决 eslint 的警告(这里会存在命名强迫症,不知道怎么命名)
const App = observer(AppInner);
export default App;

mobx 的基本使用的大致流程就是这样的。

Vue: 组件通信

T1: props 传递

正常的传递 props 给子组件,实现父传子的通信。

<!-- Father.vue -->

<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";

const name = ref("copyer");
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <Son :name="name" />
  </div>
</
template>

“ts

“`

T1: props 传递(id, class,style)

如果在组件上使用 id,class,style 或则绑定事件(click)等,那么会是什么样的效果呢?怎么拿取呢?

可以看看以前写的 vue3 中的透传 attributes

https://juejin.cn/post/7136730321923342350

<!-- Father.vue -->

<script setup lang="ts">
import Son from "./Son.vue";
const btn = () => {
  console.log("父组件的点击事件");
};
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <!-- 组件上传递 id style 点击事件-->
    <Son id="abc" style="color: red" @click="btn" />
  </div>
</
template>
<!-- Son.vue -->

<script setup lang="ts">
import { useAttrs } from "vue";
const attrs = useAttrs();

console.log("attrs======>", attrs);
const btn = () => {
  console.log("子组件的点击事件");
};
</script>

<template>
  <div class="son" @click="btn">
    <h3>我是子组件</
h3>
  </div>
</
template>

具体效果

React&Vue 系列:一篇说完组件的所有通信方式

虽然在平时开发过程中,使用这种方式很少,但也可以看成一种通信方式,传递 class 或者 style,给子组件;也可以利用绑定事件来处理一些组件互动逻辑。

T2: emit

emit 方式实现子传父

实现思路:触发父组件传递过来的方法,利用参数实现组件通信

<!-- Father.vue -->
<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";

const name = ref("copyer");
const changeName = (newName: string) => {
  name.value = newName;
};
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <!-- 给子组件传递一个函数,让子组件调用 -->
    <Son @changeName="changeName" />
  </div>
</
template>
<!-- Son.vue -->
<script setup lang="ts">
// 数组形式
// const emit = defineEmits(['changeName'])
// 对象形式
const emit = defineEmits<{
  (e: "changeName", newName: string): void;
}>();

const btn = () => {
  // 触发父组件函数,参数通信
  emit("changeName""james");
};
</script>

<template>
  <div class="son">
    <h3>我是子组件1</
h3>
    <button @click="btn">点击</button>
  </
div>
</template>

T2: v-model

「v-model」 一般用于在表单上实现数据的双向绑定。但是在 vue3 中,v-model 已经可以组件上玩出花招了,实现组件间的双向绑定。

<!-- Father.vue -->

<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";

const name = ref("copyer");
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <Son3 v-model="name" />
  </div>
</
template>
<!-- Son.vue -->

<script setup lang="ts">
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{
  (e: "update:modelValue", value: string): void;
}>();

const btn = () => {
  emit("update:modelValue""james");
};
</script>

<template>
  <div class="son">
    <h3>我是子组件</
h3>
    <span>{{ props.modelValue }}</span>
    <button @click="btn">点击</
button>
  </div>
</
template>

如果对上面的写法感到陌生的话,额外知识:Vue3 中不一样的 v-model 可以帮助你学习。

T2: ref 获取数据

使用 <script setup> 的组件是「默认关闭」的——获取到的组件实例,也就是说通过 ref 是拿取不到的。那么需要借助 编译器宏 「defineExpose」 来暴露属性,供其他组件使用。

<!-- Son.vue -->
<script setup lang="ts">
  import { ref } from "vue";
  const name = ref("我是子组件的信息");
  // 暴露了 name 属性
  defineExpose({
    name,
  });
</script>

<template>
  <div class="son">
    <h3>我是子组件</
h3>
  </div>
</
template>
<!-- Father.vue -->
<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";

const sonRef = ref();
const changeName = () => {
  console.log("sonRef======>", sonRef.value.name)
}
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <Son ref="sonRef" />
    <button @click="changeName">点击</button>
  </
div>
</template>

点击按钮,就能拿取子组件中暴露出来的数据了。

T3: provide / inject 方式

有点类似于 React 中的 Context,主要提供全局的响应数据。

<script setup lang="ts">
import Son from "./Son.vue";
import { ref, provide } from "vue";

const name = ref("copyer");
// 提供数据,供后代组件使用
provide("name", name);
const changeName = () => {
  name.value = "james";
};
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <Son />
    <button @click="changeName">点击</button>
  </
div>
</template>
<!-- Son.vue -->
<script setup lang="ts">
import { inject } from "vue";
const name = inject("name");
</script>

<template>
  <div class="son">
    <h3>我是子组件</
h3>
    <span>{{ name }}</span>
  </
div>
</template>

T3: mitt 三方库

Vue2 使用 EventBus 进行组件通信,而 Vue3 推荐使用 mitt 进行组件通信。其特点:

  • 小,仅有 200bytes
  • 支持事件监听和移除
  • 跨框架使用
  • ts 支持友好

「安装」

pnpm add mitt

「使用」第一步:创建一个事件总线

// utils/eventBus.ts

import mitt from "mitt";

// 定义注册事件名称,和触发事件 handler 参数的类型,自动推导
type Events = {
  changeName: string;
};
export default mitt<Events>();

第二步:使用

<!-- Father.vue -->
<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";
import mitt from "./utils/EventBus";

const name = ref("copyer");

// 注册事件,value 的类型也会自动推断
mitt.on("changeName"(value) => {
  name.value = value;
});
</script>

<template>
  <div class="father">
    <h3>我是父组件</
h3>
    <span>{{ name }}</span>
    <Son /
>
  </div>
</
template>
<!-- Son.vue -->
<script setup lang="ts">
import mitt from "./utils/EventBus";

const btn = () => {
  // 触发事件
  mitt.emit('changeName''james')
}
</script>

<template>
  <div class="son">
    <h3>我是子组件</
h3>
    <button @click="btn">点击</button>
  </
div>
</template>

mitt 的基本使用大致就是这样。mitt 也提供了其他函数,看看 额外知识:mitt 小知识点 吧!

T3: 状态管理 vuex

「vuex」 作为 vue 全家桶的一员就不用多做解释。

Vuex 是一个专为 Vue.js 应用程序开发的「状态管理模式 + 库」。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

「安装」

pnpm add vuex

「使用」

第一步:定义 store

// store/index.ts

const store = createStore({
  // 定义 state
  state() {
    return {
      name'copyer'
    }
  },
  // state 衍生值
  getters: {
    getNameToUpperCase(state, getters) {
      return state.name.toLocaleUpperCase();
    },
  },
  // 修改 state 的唯一途径
  mutations: {
    changeName(state, payload) {
      state.name = payload.name
    }
  },
  // 异步间接修改 state(通过触发 mutations 来改变 state)
  actions: {
    asyncChangeName(context, payload) {
      setTimeout(() => {
        context.commit("changeName", payload);
      }, 2000);
    },
  },
  modules: {
    // ...其他模块
  }
})

第二步:全局注册 store

// main.ts
const app = createApp(App)
app.use(store) // 中间件,全局注册store

第三步:在组件中使用 store

<script setup lang="ts">
import { useStore } from "vuex";
// 获取 store
const store = useStore();
  
const asynBtn = () => {
  // 同步修改通过 commit
  store.commit("changeName", { name: "james" });
};
  
const asyncBtn = () => {
  // 异步修改通过 dispatch
  store.dispatch("asyncChangeName", { name: "curry" });
};
</script>

<template>
  <div class="home">
    <div>{{ $store.state.name }}</
div>
    <button @click="asynBtn">同步修改state</button>
    <button @click="asyncBtn">异步修改state</
button>
  </div>
</
template>

这里就不介绍 modules 了,可以自己去研究一下。

Vuex 的基本使用大致就是这样了。

T3: 状态管理 pinia

「起源」

pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库(也就是 vuex5),结果写着写着,最后发现 pinia 已经具备了 vuex5 (vuex 的下一次迭代) 中的大部分内容,所以决定使用 pinia 代替 vuex5 的出现。

pinia 也是新起之秀,vue 全局桶新的一员,也是需要掌握的。

「安装」

pnpm add pinia

「使用」

第一步:创建 store

// store/userStore.ts

import { defineStore } from 'pinia'
// 创建一个 store 实例
const useUserStore = defineStore('user', {
  // 定义 state
  state() {
    return {
      name: 'copyer'
    }
  },
  // state 的变形
  getters: {
    getNameToUpperCase(state) {
      return (str) => {
        return state.name + '' + str
      }
    }
  },
  // 修改 state(同步,异步都行)
  actions: {
    async changeName(name) {
      this.name = name
    }
  }
})

第二步:创建 pinia,全局注册

// store/index.ts

import { createPinia } from "pinia";
// 创建一个 pinia 实例
const pinia = createPinia();
export default pinia;
// main.ts
import store from './store'

const app = createApp(App)
app.use(pinia) // 中间件,全局注册store

第三步:在组件中使用

<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useUserStore } from "../store/useUserStore";
const userStore = useUserStore();

// 解构,使其具有响应式
const { name } = storeToRefs(userStore);

const btn = () => {
  userStore.changeName("kobe");
};
</script>

<template>
  <div class="home">
    <div>{{ name }}</
div>
    <button @click="btn">同步修改state</button>
  </
div>
</template>

pinia 的基本使用大致就是这样了。

当然,pinia 还提供了一些批量修改的方法($patch$state$reset)在 额外知识:vuex 与 pinia 的总结和对比 都所介绍。

额外知识

RTK 异步处理

在上面已经介绍了 RTK 的基本使用流程,修改 state,同步到 view。但是还存在一种情况,就是 state 的数据源来至于服务器,也就是所谓的异步操作,那么这时候该如何写呢?

「方式一」:RTK 内部集成了 「redux-thunk」 中间件,那么就增强了 dispatch 函数,可以接受一个函数作为参数。

// Son.tsx
import type { FC } from "react";
import { useAppDispatch, useAppSelector } from "@/hooks/useAppStore";
import { changeName } from "@/store/slices/userSlice";
import { Dispatch } from "../store";

const Son: FC = () => {
  const { name } = useAppSelector((state) => state.user);
  const dispatch = useAppDispatch();

  // 该函数会注册进两个参数:dispatch 和 getState
  // 为了参数,也可以设置成一个高阶函数
  async function getName(dispatch: Dispatch{
    const res = await new Promise((resolve) => {
      setTimeout(() => {
        resolve("curry");
      }, 2000);
    });
    dispatch(changeName({ name: res }));
  }
  const btn = () => {
    // dispatch 被增强,接受一个函数
    dispatch(getName);
  };
  return (
    <>
      <h2>我是子组件</h2>
      <div>{name}</
div>
      <button onClick={btn}>异步点击</button>
    </
>
  );
};

export default Son;

「方式二」:RTK 内部也提供了一个函数 「createAsyncThunk」 专门用于异步请求数据,然后通过 「extraReducers」 添加额外的 reducer 函数,使 state 成为响应式。

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

interface IUser {
  name: string;
}
const initialState: IUser = {
  name: "copyer",
};

// 异步请求数据
export const fetchUsers = createAsyncThunk("user/fetchUsers"async () => {
  const res = await new Promise((resolve) => {
    setTimeout(() => {
      resolve("curry");
    }, 2000);
  });
  return res;
});

// 定义初始值和 reducer
const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    changeName: (state, actions) => {
      state.name = actions.payload.name;
    },
  },
  extraReducers(builder) {
    // 监听 fetchUsers 的完成状态
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      state.name = action.payload as string;
    });
  },
});

// 暴露 actions
export const { changeName } = userSlice.actions;

// 导入注册reducer
export default userSlice.reducer;

在组件中使用

dispatch(fetchUsers());

对于「方式一」「方式二」的选择,按照个人习惯还是喜欢「方式一」,也许更加符合以前的 redux 开发模式吧。

mobx 的知识要点

「第一点」:mobx 的三个核心概念

  • state 响应式(observable)数据(利用 proxy 实现的)
  • actions 修改 state 的方法
  • derivations 衍生值,根据 state 衍生出来的值,并具有缓存 (computed)。

「第二点」:定义可观察对象

利用 mobx 两个函数 makeObservable 或则 makeAutoObservable,第二个函数式加强版。简单理解,一个是手动版,一个是自动版。

class User {
  name = "copyer";
  constructor() {
    // 写法一:手动指定
    makeObservable(this, {
      name: observable,
      changeName: action,
      upperName: computed,
    });

    // 写法二:自动推断
    makeAutoObservable(this);
  }

  changeName(value: string) {
    this.name = value;
  }

  get upperName() {
    return this.name.toUpperCase();
  }
}

「第三点」:makeAutoObservable 参数解析

// 参数1:target,把谁变成响应式(可观察)
// 参数2:排除属性和方法
// 参数3:利用 autoBind 指定自动绑定 this
makeAutoObservable(this, { decrementtrue }, { autoBindtrue });

「第四点」:this 的绑定

class 中的方法不会默认绑定 this,this 指向取决于如何调用。那么对于类的属性正确的使用,就需要绑定 this。

// 也只是针对 action, 改成 action.bound ,就能正确的绑定 this
makeObservable(this, {
  name: observable,
  changeName: action.bound,
  upperName: computed,
});

「第五点」:变量监听

如果你理解 Vue3 中的 watch 和 watchEffect 这两个函数,那么这两个函数很好理解:

reaction ===> watch

autorun ===> watchEffect

它们的特点:

「autorun」:从名字上,就可以看出第一次会自动运行一次(为了收集依赖),当依赖发生变化了,再次执行。

// 这里要保证只会 autorun 初始化一次
useEffect(() => {
  autorun(() => {
    // 代码逻辑
  });
}, []);

「reaction」:接受两个参数,第一个要监听的属性,第二个就是回调函数,默认不会执行,只有当监听的属性发生变化才会执行。

useEffect(() => {
  reaction(
    () => userStore.name,
    (current, previous) => {
      console.log(current);
      console.log(previous);
    }
  );
}, []);

「第六点」:异步处理

changeName(){
    setTimeout(() => {
      runInAction(() => {
        this.name='curry'
      })
    }, 1000)
  }

Vue3 中不一样的 v-model

在 vue3 中 v-model 可以在组件上也能实现数据的双向绑定。

<!-- v-model 写法 -->
<Father v-model="msg" />
<!--  动态值绑定 modelValue 与 update:modelValue 事件 实现     -->
<Father :modelValue="msg" @update:modelValue="changeValue" />

上面这两种写法是等价的。也就是说 「v-model」 也是一种语法糖。

组件中的 v-model: 动态绑定 moelValue 属性和事件 update:modelValue 的语法糖

表单中的 v-model: 动态绑定 value 属性和事件 input/change 的语法糖

「设置动态绑定属性」

v-model 默认绑定的属性是 modelValue,但是也可以自定义的属性

<!-- v-model 写法 -->
<Father v-model:name="msg" />
<!--  动态值绑定 name 与 update:name 事件 实现     -->
<Father :name="msg" @update:name="changeValue" />

当然在子组件中的定义也要跟随修改。

「多次使用 v-model」

在组件中,也可以多次使用 v-model

<!-- v-model 写法 -->
<Father v-model:name="name" v-model:age="age" />

那么 name 和 age 属性也就双向绑定了

mitt 小知识点

简单总结一下 mitt 中提供的方法

// 监听事件
mitt.on("foo", (e) => console.log("foo", e));

// 监听所有事件,注意 handler 的参数(两个)
mitt.on("*", (type, e) => console.log(type, e));

// 触发事件
mitt.emit("foo", { a"b" });

// mitt 注销事件
mitt.off("foo");

// 注销所有事件
mitt.all.clear(); // mitt.off("*") 的区别

mitt.off(“*”) 好像注销不了,还不知道为什么

vuex 与 pinia 的总结和对比

在面前的时间里,总结一篇关于 vuex  和 pinia 使用和对比,还是比较全面的,推荐看一下。

Vue 系列:Vuex vs Pinia

https://juejin.cn/post/7218482973980573757

总结

无论是 React 的组件通信方式,还是 vue 的组件通信方式还是比较多的,在平时的开发中也非常的常见,采用的技术手段也是自己的技术选型,所以掌握多个通信方式总是没有错的。

汇集,总结一下:

React Vue
T1: props 传递,实现父传子 T1: props 传递,实现父传子
T2: 利用 props 传递回调函数,实现子传父 T2: emit 利用 props 传递回调函数,实现子传父
T2: 通过 ref 拿取子节点,实现子传父 T2: 通过 ref 拿取子节点,实现子传父
T3: context 跨组件 T3: provide/inject 跨组件
T3: 状态管理库 redux mobx T3: 状态管理库 vuex pinia

T1: id, class 等 特殊 props 传递

T2: v-model 组件双向绑定

T3: mitt

也不难发现,vue 的通信方式也更多,也可以说 vue 在语法层面比 react 相对于来说更多;当然,这些都不是重点,重点的会了就行。

关于 react 和 vue 的组件通信总结到此结束了,如果存在问题,请多多指教哟。


原文始发于微信公众号(石膏银程序员):React&Vue 系列:一篇说完组件的所有通信方式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/187243.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!