Vue与React| 组件化思想与CLI命令行

343 阅读14分钟

Vue和React作为两大主流框架,其核心概念与工具的理解和运用对开发者至关重要。

本文将深入探讨

  • Vue和React的组件化思想
  • CLI命令行工具以及相关项目结构与关键文件,

入门React 和 Vue 两个框架

一、组件与模块

模块

它是按照特定功能将JavaScript代码划分成的独立单元,其核心目标在于提升代码的可维护性与复用性,有效避免代码的混乱与冲突。

就好比在一个大型建筑项目中,将水电系统、通风系统等分别封装成独立模块,便于管理与维护。

在JavaScript生态中,常见的模块规范有CommonJS和ES Modules。以ES Modules为例,假设我们有一个math.js文件作为模块,用于封装数学运算相关的函数:

// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

在其他文件中,我们可以轻松导入并使用这些函数,如下所示:

// app.js
import { add, subtract } from './math.js';

console.log(add(5, 3));  
console.log(subtract(5, 3)); 

组件

组件则是前端开发的核心构建模块,它将JavaScript、HTML、CSS以及可能涉及的资源(如图标、字体等)整合在一起,形成一个独立且功能完整的单元。

组件是现代前端开发的新范式,其公式“组件 = JavaScript + CSS + 有一个逻辑单元的HTML + 资源”精准概括了其构成要素。

以一个常见的登录组件为例,HTML部分负责构建登录表单的结构:

<form>
  <label for="username">Username:</label>

  <input type="text" id="username" />
  <label for="password">Password:</label>

  <input type="password" id="password" />
  <button type="submit">Login</button>

</form>

CSS部分用于美化登录表单的样式,使其在视觉上更吸引人:

form {
  width: 300px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
  background-color: #f9f9f9;
}

label {
  display: block;
  margin-bottom: 5px;
}

input {
  width: 100%;
  padding: 8px;
  margin-bottom: 15px;
  border: 1px solid #ccc;
  border-radius: 3px;
}

button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}

JavaScript部分则处理登录逻辑,如表单验证、提交数据等:

const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
  e.preventDefault();
  const username = document.getElementById('username').value;
  const password = document.getElementById('password').value;
  // 这里可以添加登录验证逻辑,如发送请求到服务器验证用户信息
  console.log(`Logging in with username: ${username} and password: ${password}`);
});

将上述代码封装成一个组件后,就可以在多个页面中复用,极大提高了开发效率。

也就是说:

  • 模块是按照功能将 js 分开组成模块
  • 组件是按照功能将 js + html +css + 资源(比如: 图片、音频)封装成一个组件

Vue组件化思想与实践

Vue组件的构成与创建

Vue组件通过独特的.vue文件格式进行定义,这种方式将模板(HTML)、脚本(JavaScript)和样式(CSS)清晰地分离,同时又紧密结合在一起。一个典型的.vue文件包含三个部分:

<template>
  <!-- 这里放置HTML模板 -->
</template>

<script>
export default {
  // 这里编写JavaScript逻辑,包括数据定义、方法等
};
</script>

<style>
/* 这里定义CSS样式 */
</style>

例如,创建一个简单的计数器组件:

<template>
  <div>
    <p>{{ count }}</p>

    <button @click="increment">Increment</button>

  </div>

</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>

<style>
p {
  font-size: 18px;
}

button {
  background-color: #4CAF50;
  color: white;
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
</style>

Vue组件的功能划分与复用

Vue组件严格按照功能进行划分,这使得项目结构清晰明了,无论是开发过程还是后续维护都变得更加轻松。

就像构建一座房子,由卧室、厕所、大厅等功能明确的区域组成一样,一个Web应用也由众多功能专一的组件构建而成。

以一个电商应用为例,商品列表组件、商品详情组件、购物车组件等各司其职。这些组件可以在不同的页面或场景中灵活复用,极大提高了代码的复用性。

例如,商品列表组件可以在首页、分类页面等多个地方展示,只需根据不同页面的需求传递相应的数据即可。

Vue组件在工程中的优势

从工程化的角度来看,Vue组件具有诸多显著优势。按功能划分组件使得项目结构清晰有序,开发者能够轻松安排开发任务,团队成员之间也可以更高效地分工协作。

不同开发者专注于不同组件的开发,有效提高了开发效率。同时,组件的复用性减少了重复开发的工作量,降低了维护成本。当需要修改某个功能时,只需在对应的组件中进行调整,不会影响整个应用程序。

而且,Vue组件以标签的方式嵌入页面,如<Counter/>,这种方式使页面结构一目了然,可读性极佳,方便其他开发者理解和维护代码。

React组件化思想与实践

React组件的定义与类型

React组件主要有函数组件和类组件两种形式。函数组件简洁高效,专注于UI展示,在React 16.8引入Hooks后,其功能得到了极大的拓展,能够处理状态管理和副作用等操作。例如,以下是一个简单的函数组件用于显示欢迎消息:

function WelcomeMessage(props) {
  return <h1>Welcome, {props.name}!</h1>;
}

类组件则提供了更丰富的生命周期方法和状态管理能力,适用于复杂的业务逻辑场景。例如,一个类组件用于管理计数器状态:

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  increment() {
    this.setState((prevState) => ({
      count: prevState.count + 1
    }));
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>

        <button onClick={() => this.increment()}>Increment</button>

      </div>

    );
  }
}

React组件的通信机制

React组件间通信方式多样且灵活。在父子组件通信中,父组件向子组件传递数据通过props实现,子组件接收props并渲染相应内容。例如:

// 父组件
class ParentComponent extends Component {
  constructor() {
    super();
    this.state = {
      message: 'Hello from parent!'
    };
  }

  render() {
    return (
      <div>
        <ChildComponent message={this.state.message} />
      </div>

    );
  }
}

// 子组件
function ChildComponent(props) {
  return <div>{props.message}</div>;
}

子组件向父组件传递数据则通过父组件传递回调函数,子组件在适当的时候调用该回调函数。例如:

// 父组件
class ParentComponent extends Component {
  constructor() {
    super();
    this.state = {
      childMessage: ''
    };
  }

  handleChildMessage(message) {
    this.setState({
      childMessage: message
    });
  }

  render() {
    return (
      <div>
        <ChildComponent onMessage={this.handleChildMessage} />
        <p>Received from child: {this.state.childMessage}</p>

      </div>

    );
  }
}

// 子组件
function ChildComponent(props) {
  const handleClick = () => {
    props.onMessage('Hello from child!');
  };

  return <button onClick={handleClick}>Send Message to Parent</button>;
}

对于非父子组件通信,React提供了Context API,用于跨组件层级共享状态,适用于全局状态管理场景,如主题切换、用户认证信息等。例如:

// 创建Context
const ThemeContext = React.createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>

  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>

  );
}

function ThemedButton() {
  const theme = React.useContext(ThemeContext);
  return <button style={{ backgroundColor: theme === 'dark'? 'black' : 'white' }}>Click me</button>;
}

React组件的生命周期(类组件)

React类组件具有完整的生命周期方法,涵盖挂载(Mount)、更新(Update)和卸载(Unmount)阶段。

在挂载阶段,constructor用于初始化组件状态和绑定事件处理函数(可选),render方法返回要渲染的React元素,componentDidMount在组件挂载后立即调用,适合进行依赖DOM的操作、发送网络请求或添加订阅。例如:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null
    };
  }

  componentDidMount() {
    // 发送网络请求获取数据
    fetch('https://example.com/api/data')
  .then((response) => response.json())
  .then((data) => this.setState({ data }));
  }

  render() {
    return <div>{this.state.data? this.state.data : 'Loading...'}</div>;
  }
}

在更新阶段,componentDidUpdate在组件更新后调用,可在此对DOM进行操作或根据props变化决定是否发送网络请求。例如:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidUpdate(prevProps) {
    if (prevProps.count!== this.props.count) {
      // 根据props变化进行操作
      console.log('Count has changed');
    }
  }

  handleClick() {
    this.props.onIncrement();
  }

  render() {
    return (
      <div>
        <p>Count: {this.props.count}</p>

        <button onClick={() => this.handleClick()}>Increment</button>

      </div>

    );
  }
}

在卸载阶段,componentWillUnmount在组件卸载及销毁之前直接调用,用于执行清理操作,如清除定时器、取消网络请求或订阅等。例如:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      timer: null
    };
  }

  componentDidMount() {
    const timer = setInterval(() => {
      console.log('Timer ticking');
    }, 1000);
    this.setState({ timer });
  }

  componentWillUnmount() {
    clearInterval(this.state.timer);
  }

  render() {
    return <div>My Component</div>;
  }
}

Vue与React CLI命令行工具

Vue CLI命令行

Vue CLI是Vue.js官方提供的强大脚手架工具,主要用于快速搭建Vue项目,尤其适用于复杂大型项目的开发。它提供了丰富的预设配置和插件,方便开发者根据项目需求进行定制化开发。

使用vue create命令可以创建一个新的Vue项目,例如vue create my-vue-app。在创建过程中,开发者可以选择不同的预设配置,如默认配置、手动选择特性(包括是否使用TypeScript、Router、Vuex、CSS预处理器等)、使用历史模式的Router等。

React CLI命令行

React提供了create-react-app命令行工具,用于快速创建React项目。这个工具简化了项目的初始搭建过程,为开发者生成一个包含基本配置和文件结构的React应用模板。

例如,在命令行中执行npx create-react-app my-react-app,将会创建一个名为my-react-app的新React项目。

项目结构与关键文件对比

1. package.json

在Vue和React项目中,package.json都是核心配置文件。在Vue项目中,它记录项目信息、管理依赖关系等。开发依赖可能包括@vue/cli-service等用于构建和开发的工具,生产依赖则包含Vue核心库以及项目中实际使用的其他库。例如:

{
  "name": "my-vue-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^5.0.8",
    "@vue/cli-plugin-eslint": "^5.0.8",
    "@vue/cli-service": "^5.0.8",
    "eslint": "^8.45.0",
    "eslint-plugin-vue": "^9.17.0"
  }
}

在React项目中,package.json同样记录项目基本信息、依赖关系等。开发依赖如react-scripts用于启动开发服务器、构建项目等,生产依赖主要是React相关的核心库,如reactreact-dom。例如:

{
  "name": "my-react-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "react-scripts": "^5.0.1"
  }
}

2. 入口文件

Vue项目的入口文件通常是src/main.js,主要负责创建Vue应用实例并挂载到DOM上。例如:

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);
app.mount('#app');

React项目的入口文件一般是src/index.js,在这个文件中,主要进行React应用的渲染操作,通过ReactDOM.render方法将React组件挂载到index.html中的根DOM节点上。例如:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>

);

3. 根组件

Vue的根组件是src/App.vue,采用三段式结构,分别负责模板、逻辑和样式。例如:

<template>
  <div>
    <h1>{{ message }}</h1>

  </div>

</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  }
};
</script>

<style>
h1 {
  color: blue;
}
</style>

React的根组件可以是src/App.js,可以是函数组件或类组件形式。函数组件示例:

function App() {
  return (
    <div>
      <h1>Hello, React!</h1>

    </div>

  );
}

export default App;

类组件示例:

  // 1.创建类组件
       class MyCompont extends React.Component{
          render() {
            // render是放在哪里的 ? - 类MyCompont的原型对象上 , 供实例调用 , 那实例在哪里 ?没有 new MyComponent() ?
            // render中的this 是谁 ? - MyComponent 的实例对象 。
            console.log(this)
            return <h2>hello , 我是用函数定义的组件</h2>
          }
       }
       // 2. 渲染组件到页面
       ReactDOM.render(<MyCompont/>,document.getElementById('test'))



       /*

        执行了ReactDoM.render(<MyComponent>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用类定义的,随后new出该类的实例出来,并通过该实例调用原型上的render 方法 
        3.将返回的虚拟DOM转为真实DOM,随后呈现在页面中

        */

组件化在Vue与React中的共性与差异

1. 共性

  • 提升开发效率:Vue和React的组件化均能有效提升开发速度。以构建一个大型电商应用为例,无论是Vue还是React,都可将商品列表、购物车、用户信息等模块拆分成独立组件。开发者能并行开发这些组件,避免重复劳动,显著缩短项目周期。比如,在多个页面均需使用的商品搜索组件,只需开发一次,即可在不同页面复用,大大提高了开发效率。
  • 增强代码维护性:两种框架的组件化都使项目结构清晰,职责分明。当应用规模增大时,如一个复杂的企业级应用,无论是基于Vue还是React开发,若某个组件出现问题,都能迅速定位到对应的组件代码进行修复,而不会牵一发而动全身。例如,若订单处理组件发生故障,只需关注该组件的逻辑,不会影响其他无关功能。
  • 优化代码复用性:在Vue和React中,组件的复用性都很强。无论是小型项目中的通用按钮组件,还是大型项目中的复杂布局组件,都可在不同场景下重复使用。只需根据具体需求传递不同参数,就能适应各种变化。例如,一个弹窗组件,可在不同页面用于提示信息、确认操作等,只需调整显示内容和样式参数。

2. 差异

  • 组件定义形式:Vue采用独特的.vue文件格式定义组件,将模板、逻辑和样式整合在一个文件中,结构清晰明了。例如:
<template>
  <div>
    <h1>{{ title }}</h1>

  </div>

</template>

<script>
export default {
  data() {
    return {
      title: 'Vue Component'
    };
  }
};
</script>

<style>
h1 {
  color: blue;
}
</style>

React则有函数组件和类组件两种形式。函数组件简洁灵活,适用于简单UI展示,如:

function MyComponent(props) {
  return <h1>{props.title}</h1>;
}

类组件提供更多生命周期方法和状态管理功能,适合复杂逻辑处理,如:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: 'React Component'
    };
  }

  render() {
    return <h1>{this.state.title}</h1>;
  }
}
  • 模板语法特点:Vue使用基于HTML的模板语法,通过指令实现数据绑定和事件处理,符合传统前端开发者习惯。例如,使用v-bind绑定属性,v-on绑定事件:
<template>
  <div>
    <input v-model="message" />
    <button v-on:click="sendMessage">Send</button>

  </div>

</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  methods: {
    sendMessage() {
      console.log(this.message);
    }
  }
};
</script>

React使用JSX语法,将HTML与JavaScript融合,更具灵活性。例如:

function InputWithButton(props) {
  const handleClick = () => {
    console.log(props.value);
  };

  return (
    <div>
      <input value={props.value} onChange={props.onChange} />
      <button onClick={handleClick}>Send</button>

    </div>

  );
}
  • 状态管理模式:Vue除了组件内部data选项管理状态外,还提供Vuex用于全局状态管理,其遵循严格的状态变更流程,包含状态、突变、动作和获取器等概念。例如:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2;
    }
  }
});

// 在组件中使用
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex';

export default {
  computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
  },
  methods: {
...mapMutations(['increment']),
...mapActions(['incrementAsync'])
  }
};

React主要通过setState(类组件)和useStateuseReducer(函数组件)管理状态,对于跨组件状态共享,使用Context API。例如:

// 使用useState
import React, { useState } from'react';

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>

      <button onClick={handleClick}>Increment</button>

    </div>

  );
}

// 使用Context API
const ThemeContext = React.createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>

  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>

  );
}

function ThemedButton() {
  const theme = React.useContext(ThemeContext);
  return <button style={{ backgroundColor: theme === 'dark'? 'black' : 'white' }}>Click me</button>;
}
  • 组件通信方式:在父子组件通信上,Vue和React类似,父组件通过属性传递数据给子组件,子组件通过事件触发父组件函数来传递消息。但在非父子组件通信方面,Vue提供EventBus(简单事件总线,适用于小型项目)和Vuex(用于中大型项目全局状态管理),而React主要依赖Context API,同时也可通过状态提升等方式实现有限的非父子组件通信。例如,在Vue中使用EventBus
// EventBus.js
import Vue from 'vue';
export const EventBus = new Vue();

// 发送事件
EventBus.$emit('message', 'Hello from EventBus');

// 接收事件
EventBus.$on('message', (data) => {
  console.log(data);
});

组件化思想

组件化在前端开发中十分重要 。

在开发效率提升上,组件化允许开发者将复杂页面拆分成多个小而独立的组件,并行开发。

例如,在开发一个大型新闻资讯平台时,新闻列表、新闻详情、侧边栏等组件可由不同开发者同时开发,大大加快项目进度。

同时,复用已有的组件减少了重复编写代码的工作量,如通用的分页组件可在多个列表页面使用。

对于代码维护而言,清晰的组件结构使代码易于理解和修改。当需求变更时,只需关注相关组件。

例如,若新闻平台的页面布局调整,只需修改布局相关组件,不会影响其他功能组件。

在团队协作方面,组件化明确了各成员的职责范围。不同成员专注于特定组件开发,降低了代码冲突风险,提高了协作效率。

比如,前端团队中,有的成员负责用户界面组件,有的负责数据交互组件。

从代码复用角度看,组件可在不同项目或场景中重复使用,提高了代码的利用率和一致性。

例如,一个通用的模态框组件可应用于不同类型的项目中,只需根据项目风格调整样式。