前端圈

分享与交流前端开发相关知识

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

当您第一次想了解前端技术时,会被很多工具选择所迷惑,例如React,Vue,Angular,Svelte等。当然,我们不知道是否不尝试其中一种工具。 ,当然所有这些技术都有其优缺点。

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

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


建立项目

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

1.根目录结构

组件文件夹中有容器和演示文件夹。不同之处在于,呈现组件侧重于UI元素,而容器组件则控制逻辑/存储数据部分,该逻辑/存储数据部分将显示在组件容器中。


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

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

2.使用jsx和打字稿

因此,让我们开始安装一些所需的依赖项。我们可以通过键入以下命令来做到这一点:

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 dan babel loader

npm i --save-dev [email protected]^7.0.0-0 [email protected]^8.0.6 
npm i --save-dev @babel/[email protected]^7.6.4 @babel/[email protected]^7.6.3 

安装所有依赖项后,我们必须在.babelrc打开文件时设置其他配置,然后添加config .babelrc,我们还需要设置webpack loader webpack config

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

别忘了,您还需要在其中添加一些配置.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中实现反应模式。

组件和道具

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

这里我使用Vue.componentcreateComponent (VUE成分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中的儿童

子级是一个组件,用于在调用该组件时显示您在开始和结束标记之间包含的所有内容。

为了访问此组件,我们可以使用 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创建组件实例时,在初始道具解析后立即调用:。在生命周期方面,它在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完全相同的新对象,并返回原始对象的反应式代理。
  • watch功能需要功能。它跟踪内部的反应变量,因为组件为模板执行此操作。当我们修改在传递的函数中使用的反应变量时,给定的函数将再次运行。
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有四个主要概念:reduceactionaction创造者store。在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 
    }
}

减速器

精简器指定应用程序的状态如何响应发送到商店的操作而改变。然后,可以将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在一个根化简函数中调度动作时,使用来调用所有化简。这非常有用:)

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

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

商店

一个是存储应用程序的状态的地方。存储并保存应用程序的整个状态树,该状态树将引用该对象的对象与带有几种方法的对象一起放在一起。Redux应用程序中只有一个存储。

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,然后导入商店并将其传递给every 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上获得

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注