设计模式:Vue 感觉像 React - TypeScript 🔥

2025-06-10

设计模式:Vue 感觉像 React - TypeScript 🔥

封面照片由Ricardo Gomez AngelUnsplash上拍摄。

当你第一次想要学习一门前端技术时,你会被众多的工具选择所困扰,例如 React、Vue、Angular、Svelte 等。当然,如果我们不尝试其中任何一种,我们就不会知道,当然所有这些技术都有其优点和缺点。

但在本文中,我们不会讨论哪个是最好的,而是讨论 React 开发人员如何以相同的模式轻松掌握这两个框架(React 和 Vue)。

所以,这是一段漫长的旅程!做好准备!😃


设置项目

首先我们要做的是建立项目,让我们先创建一个目录结构。

1. 根目录结构

组件文件夹中包含容器和展示文件夹。区别在于,展示组件专注于 UI 元素,而容器组件则负责规范逻辑/存储数据部分,这些数据部分将显示在组件容器中。


    ├── src
    | ├── assets
    | ├── components
    |   ├── container
    |   ├── presentational
    ├── redux
    | ├── action
    | ├── reducer
    ├─

你可以自由设置你喜欢的目录结构,这是我创建项目的目录结构

2. 使用 jsx 和 typescript

那么,让我们先安装一些需要的依赖项。我们可以输入以下命令来执行此操作:

npm i --save-dev typescript babel-preset-vca-jsx
npm i --save-dev @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime 
npm i --save-dev @babel/preset-typescript @types/webpack-env source-map-loader 
npm uninstall babel-plugin-transform-runtime 

我们需要卸载这个包babel-plugin-transform-runtime,因为我们已经安装了最新版本@babel/plugin-transform-runtime

然后,我们必须设置一些额外的配置,因为一些依赖项需要支持的 Babel 版本

注意:我碰巧使用了这个样板:Vue Webpack 模板,您可以选择任何样板。

更新你的 babel core 和 babel loader

npm i --save-dev babel-core@^7.0.0-0 babel-loader@^8.0.6 
npm i --save-dev @babel/core@^7.6.4 @babel/preset-env@^7.6.3 

安装完所有依赖项后,我们必须在.babelrc打开文件时设置附加配置,然后添加配置.babelrc我们还需要设置 webpack 加载器webpack 配置

注意:对于 TypeScript 加载器,您可以使用Awesome TypeScript Loader

别忘了,你还需要添加一些配置.eslintrc.js

rules: {
    'import/extensions': ['error', 'always', {
      jsx: 'never',
      ts: 'never',
      tsx: 'never'
    }],
}

接下来,创建新文件tsconfig.json并遵循此配置tsconfig.json

注意:如果你想使用TSLint,你可以按照此步骤配置 TSLint

添加所有配置后,好了!现在是时候将所有项目文件扩展名从.jsx/.js替换为.tsx/.ts

3.安装其他依赖项

npm i --save @vue/composition-api vuejs-redux redux @types/redux 

主要概念

作为非常流行的前端工具,这两种工具具有相同的功能,例如双向数据绑定、模板、路由、组件、依赖注入等等。

相似但不相同,这两个工具之间存在一些差异,即在编写语法、渲染组件、管理状态和数据方面。因此,在本节中,我们将逐一揭秘如何在 Vue 中实现 React 模式。

组件和道具

组件是特殊类型的指令,例如 JavaScript 函数,它们将显示为单独的部分并可重复使用。

这里我使用Vue.componentcreateComponent (vue Composition API)你可以同时使用

在渲染组件时,两者有很大不同。React 将组件定义为类或函数,而 Vue 将组件定义为对象。

export default createComponent({
    name: 'ComponentProps',
    props: {
        name: String,
        authorName: Array as () => string[]
    },
    setup(props) {
        return () => (
            <div className="components-props">
                <h2>{props.name}</h2>
                <p>{props.authorName}</p>
            </div>
        )
    }
})

我们不再需要template再次使用,只需像 React 一样使用 JSX即可 🙂

render () {
  return (
      <ComponentProps 
         name="Your name here" 
         commentId={['Name1', 'Name2']} 
      />
  )
}

条件渲染

条件渲染的工作方式与 JavaScript 中的条件工作方式相同,我们可以使用三元或条件运算符。

export default createComponent({
    name: 'ConditionalRendering',
    props: {
        show: Boolean
    },
    setup(props) {
        return () => props.show ? <p>True Condition</p> : <p>False Condition</p>
    }
})
render() {
   return <ConditionalRendering show={false}/>
}

处理事件

在 Vue JS 中,处理事件时,Vue 会提供指令来 v-on 处理这些事件。由于我们已经使用了 JSX,所以不再需要它,我们可以像在 React 中一样使用 JSX 属性 :)

export default createComponent({
    setup(props) {
        return () => (
            <button onClick={props.handleButtonClick}>
                Click Event
            </button>
        )
    },
    props: {
        handleButtonClick: Function as () => void
    }
})
render () {
  return (
       <HandlingEvent 
          handleButtonClick={() => alert("Click event. This works!")} 
       />
  )
}

JSX 中的子项

Children 是一个组件,用于在调用该组件时显示在开始和结束标记之间包含的任何内容。

为了访问该组件,我们可以使用 slots函数作为内容分发出口。

export default Vue.component('Children', {
    render() {
        return (
            <div className="children">
                {this.$slots.default}
            </div>
        )
    }
})
render () {
  return (
     <div className='container'>
        <Children>
          {/* what is placed here is passed as children */}
        </Children>
     </div>
  )
}

生命周期和钩子

生命周期是一种规范组件生命周期各个阶段的方法,它们有各自的用途

  • setup: 在组件实例创建时,在初始 props 解析之后立即调用。从生命周期角度来看,它在beforeCreate钩子之前调用。
  • onBeforeMount在渲染过程运行之前执行的函数。
  • onMounted首次渲染完成后仅调用一次的函数。通常,此函数用于执行任何可能产生副作用的操作,例如 AJAX 请求。
  • onUnmounted执行该函数是为了从 DOM 中消除或删除组件。
import {
    createComponent,
    reactive as useState,
    onBeforeMount as componentWillMount,
    onMounted as componentDidMount,
    onUnmounted as componentWillUnmount
} from '@vue/composition-api';

const LifecycleHooks = createComponent({
    setup() {
        const state = useState<{ loading: boolean, users: object }>({
            loading: false,
            users: []
        })

        componentWillMount(() => {
            console.log("Component before mount")
        })

        componentDidMount(() => {
            const API_URL = 'https://jsonplaceholder.typicode.com/users'
            fetch(API_URL)
                .then(res => res.json() as Promise<any>)
                .then(data => {
                    state.users = data,
                        state.loading = !state.loading;
                })
                .catch((err: Error) => {
                    throw err
                })
            console.log("Component Mounted")
        });

        componentWillUnmount(() => {
            console.log("Component Will Unmount")
        })

        return () => (
            <div className="lifecycle-hooks">
                {state.loading ? JSON.stringify(state.users) : <span>Loading...</span>}
            </div>
        )
    }
})

export default LifecycleHooks

是的,我用它 as ... 来导入模块,这只是命名,所以它看起来和 React 中的方法名一样

  • reactive函数相当于 Vue 2 的,Vue.observable()它将返回一个与 obj 完全相同的新对象,并返回原始对象的反应式代理。
  • watchfunction 需要一个函数作为参数。它会跟踪内部的反应变量,就像组件为模板所做的那样。当我们修改传入函数内部使用的反应变量时,给定的函数会再次运行。
import {
    createComponent,
    reactive as useState,
    watch as useEffect
} from '@vue/composition-api';

const LifecycleHooks = createComponent({
    setup() {
        const state = useState<{ count: number }>({
            count: 0
        })

        /* => Re-run it whenever the dependencies have changed */
        useEffect(() => state.count, (nextState, prevState) => {
            console.log(nextState, '<= this is nextState')
            console.log(prevState, '<= this is prevState');
        })

        return () => (
            <div className="lifecycle-hooks">
                <button onClick={() => state.count++}>
                    Update Value
                </button>
            </div>
        )
    }
})

Redux 和 Vue

你肯定已经知道什么是 Redux 了吧?没错!Redux 是一个面向 JavaScript 应用的独立状态管理库框架。与 Vuex 不同,Redux 可以在任何框架中使用。

Redux 有 4 个主要概念:reducersactionsaction creatorsstore。在 Redux 中,状态是不可变的纯函数。以下是一些关于 Vue 中 Redux 的知识:

行动

操作是简单的 JavaScript 对象,代表将数据从应用程序发送到商店的信息负载。操作具有类型和可选负载。

export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const RESET = 'RESET'


export const increment = () => {
    return { 
        type: INCREMENT 
        // your payload here
    }
}

export const decrement = () => {
    return { 
        type: DECREMENT 
    }
}

export const reset = () => {
    return { 
        type: RESET 
    }
}

Reducers

Reducer 指定应用程序的状态如何响应发送到 store 的操作而变化。Reducer 可以组合成一个根 Reducer,以管理应用程序的所有状态。

type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'RESET' };

const Counter = (state: number = 0, action: Action) => {
    switch (action.type) {
        case 'INCREMENT': {
            return state + 1;
        }
        case 'DECREMENT': {
            return state - 1;
        }
        case 'RESET': {
            return state
        }
        default: return state
    }
}

export default Counter

combineReducers在一个根 Reducer 函数中调度 Action 时,使用to 调用所有 Reducer。这非常有用:)

import { combineReducers } from 'redux'
import userReducer from './reducer/user.reducer'

export default combineReducers({
    user: userReducer
    // your another reducer here
})

店铺

Store是存储应用程序状态的地方。Store 保存了应用程序的整个状态树该状态树包含指向对象的一些方法。Redux 应用程序中只有一个 Store。

import Vue from 'vue'
import { createStore } from 'redux'

import Provider from 'vuejs-redux';
import RootReducer from './rootReducer'

const store = createStore(RootReducer);

export default Vue.component('Provider', {
    render() {
        return (
            <Provider 
                mapStateToProps={this.mapStateToProps} 
                mapDispatchToProps={this.mapDispatchToProps} 
                store={store}> 
                {this.$scopedSlots.default}
            </Provider>
        )
    },

    props: ['mapStateToProps', 'mapDispatchToProps'],

    components: {
        Provider
    }
})

我们还可以创建一个自定义提供程序,接收 mapStateToProps 和 mapDispatchToProps 作为 props,然后导入存储并将其传递给每个Provider

import Vue from 'vue';
import ContextConsumer from './redux';
import * as actions from './redux/action/user.action';

import ComponentContainer from './components/container/component-wrap';

export default Vue.component('App', {
  render() {
   return (
      <ContextConsumer 
          mapStateToProps={this.mapStateToProps} 
          mapDispatchToProps={this.mapDispatchToProps}>
            {({ incrementAction, userData }) => (
                <ComponentContainer>
                    <SingleComponent
                      value={userData.user}
                      handleClick={incrementAction} 
                    />
                </ComponentContainer>
            )}
      </ContextConsumer>
    )
  },

  components: {
    ContextConsumer
  },

  methods: {
    mapStateToProps(state) {
      return {
        userData: state
      }
    },
    mapDispatchToProps(dispatch) {
      return {
        incrementAction: () => dispatch(actions.increment())
      }
    }
  }
})

高阶组件

高阶组件 (HOC) 是 React 中用于复用组件逻辑的高级技术。HOC 不属于 React API,而是一种源于 React 组合特性的模式。

如果你理解了高阶函数(HOF)的概念,当然做HOC就会非常容易,因为HOC就是HOF的一种实现:)

import Vue from 'vue'

const useDataFetchingHOC = (WrappedComponent: JSX.IntrinsicElements) => (urlParam: string) => {
    return Vue.component('HOCFetch', {
        data: () => ({
            fetchData: null
        }),
        mounted: function() {
            fetch(urlParam)
                .then(response => {
                    if (!response.ok) { throw new Error(response.statusText) }
                    return response.json() as Promise<any>;
                })
                .then(data => this.fetchData = data)
                .catch((err: Error) => {
                    throw err
                })
        },

        render(createElement) {
            return !this.fetchData ? createElement('span', 'Loading Fetch...') :
                createElement(WrappedComponent, {
                    attrs: this.$attrs,
                    props: this.$props,
                    on: this.$listeners
            })
        }
    })
};

export default useDataFetchingHOC
import { createComponent } from '@vue/composition-api'
import useDataFetchingHOC from '../presentational/hoc-component'

const dataSourceUrl = "https://jsonplaceholder.typicode.com/users";

const ContentSite = createComponent({
    setup() {
      return () => (
        <div className="content">
          <p>Yes, i'm in HOC</p>
        </div>
      )
    }
  })

export default useDataFetchingHOC(ContentSite)(dataSourceUrl)

感谢阅读

感谢阅读,希望你喜欢这篇文章,并希望它能给你的工作带来一些启发。诚然,Vue 和 React 是非常酷的前端工具,并且受到众多用户的青睐。所以,继续尝试和学习新事物吧,别忘了永远相信自己!😎

该项目的完整源代码可在Gitlab上找到。

鏂囩珷鏉ユ簮锛�https://dev.to/natserrat/design-patterns-vue-feels-like-react-typescript-38in
PREV
将 React 表格​​渲染优化 160 倍!!!
NEXT
🌟协调应用性能:🎵跨平台测试的艺术 GenAI LIVE!| 2025 年 6 月 4 日