MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Svelte 代码风格:保持一致性与可读性

2021-07-102.1k 阅读

一、命名规范

(一)变量命名

  1. 驼峰命名法:在 Svelte 中,变量命名通常遵循驼峰命名法(camelCase)。这与大多数 JavaScript 代码风格保持一致,使得代码易于阅读和理解。例如,当我们定义一个表示用户名字的变量时,可以这样写:
<script>
    let userName = 'John Doe';
</script>

驼峰命名法使得变量名清晰地表达其含义,从单词的组合中能快速知晓变量所代表的内容。避免使用下划线分隔单词(如 user_name),虽然在某些语言中这是常见的命名方式,但在 JavaScript 和 Svelte 的生态中,驼峰命名法更为流行。

  1. 语义化命名:变量名应具有明确的语义,能够准确反映其用途。比如,如果我们在一个电子商务应用中,定义一个表示商品价格的变量,使用 productPrice 就比简单地用 pricep 要好得多。因为 productPrice 明确指出这是商品的价格,而 price 可能在不同上下文中代表不同类型的价格,p 则过于简略,无法清晰传达含义。
<script>
    let productPrice = 19.99;
</script>
  1. 避免单字符变量名(除了常用迭代变量):除非是在循环迭代中使用 ijk 等传统的单字符变量来表示索引,其他情况下应避免使用单字符变量名。例如,在遍历数组时,这样使用单字符变量是合理的:
<script>
    let numbers = [1, 2, 3, 4, 5];
    for (let i = 0; i < numbers.length; i++) {
        console.log(numbers[i]);
    }
</script>

但如果在其他逻辑中,用 a 来表示某个复杂的数据结构或逻辑值,就会使代码难以理解和维护。

(二)函数命名

  1. 动词开头:函数名通常以动词开头,描述函数所执行的操作。例如,如果一个函数用于获取用户信息,命名为 getUserInfo 是合适的。这种命名方式让调用者一眼就能明白函数的功能。
<script>
    function getUserInfo() {
        return { name: 'Jane Smith', age: 30 };
    }
</script>
  1. 清晰描述操作:函数名要准确描述其操作,避免过于模糊或笼统的命名。比如,不要将一个处理用户登录逻辑的函数命名为 handleUser,而应命名为 handleUserLogin,这样更清晰地表明函数的具体职责。
<script>
    function handleUserLogin(username, password) {
        // 登录逻辑
        if (username === 'admin' && password === '123456') {
            return true;
        }
        return false;
    }
</script>
  1. 遵循约定俗成的命名模式:在一些特定场景下,Svelte 遵循 JavaScript 的一些约定俗成的命名模式。例如,用于处理事件的函数通常以 handle 开头,如 handleClickhandleSubmit 等。
<script>
    function handleClick() {
        console.log('Button clicked!');
    }
</script>
<button on:click={handleClick}>Click me</button>

(三)组件命名

  1. 大写驼峰命名法:Svelte 组件使用大写驼峰命名法(PascalCase),这有助于将组件与普通变量和函数区分开来。例如,一个用于显示用户资料的组件可以命名为 UserProfile.svelte
<!-- UserProfile.svelte -->
<script>
    let user = { name: 'Bob Johnson', age: 25 };
</script>

<div>
    <h2>{user.name}</h2>
    <p>Age: {user.age}</p>
</div>
  1. 以功能或用途命名:组件名应清晰地反映其功能或用途。如果是一个导航栏组件,命名为 NavigationBar.svelte 就很合适,这样在其他地方引入和使用该组件时,开发者能迅速了解其作用。
<!-- NavigationBar.svelte -->
<script>
    let links = ['Home', 'About', 'Contact'];
</script>

<ul>
    {#each links as link}
        <li>{link}</li>
    {/each}
</ul>
  1. 避免无意义或过于通用的命名:不要使用像 Component1GenericComponent 这样无意义或过于通用的命名。这会使代码的可读性和可维护性大打折扣,特别是在大型项目中有多个组件时,难以区分每个组件的具体功能。

二、代码结构

(一)组件结构

  1. 单一职责原则:每个 Svelte 组件应该遵循单一职责原则,即一个组件只负责一项特定的功能。例如,不要将用户登录和用户注册功能放在同一个组件中,而应该分别创建 LoginComponent.svelteRegisterComponent.svelte
<!-- LoginComponent.svelte -->
<script>
    let username = '';
    let password = '';

    function handleSubmit() {
        // 登录逻辑
    }
</script>

<form on:submit|preventDefault={handleSubmit}>
    <label>Username:
        <input type="text" bind:value={username}>
    </label>
    <label>Password:
        <input type="password" bind:value={password}>
    </label>
    <button type="submit">Login</button>
</form>
<!-- RegisterComponent.svelte -->
<script>
    let newUsername = '';
    let newPassword = '';

    function handleRegister() {
        // 注册逻辑
    }
</script>

<form on:submit|preventDefault={handleRegister}>
    <label>New Username:
        <input type="text" bind:value={newUsername}>
    </label>
    <label>New Password:
        <input type="password" bind:value={newPassword}>
    </label>
    <button type="submit">Register</button>
</form>

这样每个组件的职责清晰,便于维护和复用。

  1. 模板、脚本和样式的分离:在 Svelte 组件中,模板(HTML 部分)、脚本(JavaScript 部分)和样式(CSS 部分)应该清晰分离。模板部分负责组件的结构和展示,脚本部分处理逻辑,样式部分定义外观。
<!-- MyComponent.svelte -->
<script>
    let count = 0;
    function increment() {
        count++;
    }
</script>

<button on:click={increment}>Click me {count} times</button>

<style>
    button {
        background-color: blue;
        color: white;
    }
</style>

这种分离使得代码结构清晰,不同功能的代码各自集中,便于开发者理解和修改。

  1. 组件层次结构:在大型应用中,合理规划组件的层次结构至关重要。通常,顶层组件负责整体布局和路由,中层组件处理业务模块的划分,底层组件则是一些可复用的基础组件,如按钮、输入框等。例如,在一个博客应用中,可能有一个 App.svelte 作为顶层组件,负责设置整体布局和路由到不同页面,如 BlogList.svelteBlogDetail.svelte 作为中层组件,分别展示博客列表和博客详情,而 Button.svelteTextInput.svelte 等作为底层的基础组件被这些中层组件复用。

(二)项目结构

  1. 按功能模块划分:将项目按照功能模块进行划分是一种常见且有效的方式。例如,在一个电子商务项目中,可以有 productscartcheckout 等模块。每个模块可以包含相关的组件、样式和逻辑代码。
src/
├── products/
│   ├── ProductList.svelte
│   ├── ProductDetail.svelte
│   ├── productStyles.css
│   └── productLogic.js
├── cart/
│   ├── Cart.svelte
│   ├── cartStyles.css
│   └── cartLogic.js
├── checkout/
│   ├── Checkout.svelte
│   ├── checkoutStyles.css
│   └── checkoutLogic.js
└── main.js

这样的结构使得项目的功能模块清晰,便于开发、维护和扩展。

  1. 公共资源的管理:对于一些公共的资源,如样式、工具函数等,应该有专门的目录进行管理。例如,可以创建一个 shared 目录,将公共的 CSS 文件、JavaScript 工具函数等放在这里。
src/
├── shared/
│   ├── styles/
│   │   ├── global.css
│   │   └── utility.css
│   └── utils/
│       └── helperFunctions.js
├── products/
├── cart/
└── checkout/

这样可以避免在不同模块中重复编写相同的代码,提高代码的复用性。

  1. 配置文件的放置:项目的配置文件,如 API 端点配置、环境变量配置等,应该放在一个合适的位置。通常,可以在项目根目录下创建一个 config 目录,将配置文件放在这里。例如,config/apiConfig.js 可以存储 API 的相关配置信息。
src/
├── config/
│   └── apiConfig.js
├── shared/
├── products/
├── cart/
└── checkout/

这样的结构便于统一管理和修改配置信息,也提高了项目的可部署性。

三、样式规范

(一)局部样式

  1. 组件级别的样式隔离:Svelte 的一大优势是组件级别的样式隔离。在组件内部定义的样式只对该组件生效,不会影响其他组件。这使得我们可以在不同组件中使用相同的类名而不用担心冲突。例如:
<!-- ComponentA.svelte -->
<script>
    // 组件逻辑
</script>

<div class="box">
    <p>Content of ComponentA</p>
</div>

<style>
   .box {
        background-color: lightblue;
        padding: 10px;
    }
</style>
<!-- ComponentB.svelte -->
<script>
    // 组件逻辑
</script>

<div class="box">
    <p>Content of ComponentB</p>
</div>

<style>
   .box {
        background-color: lightgreen;
        padding: 15px;
    }
</style>

在上述例子中,ComponentAComponentB 都有一个名为 .box 的类,但它们的样式互不干扰。

  1. 使用 BEM 命名规范(可选):虽然 Svelte 组件样式隔离减少了命名冲突的问题,但采用 BEM(Block - Element - Modifier)命名规范可以进一步提高样式的可读性和可维护性。例如,在一个按钮组件中:
<!-- Button.svelte -->
<script>
    let isDisabled = false;
</script>

<button class="button {isDisabled? 'button--disabled' : ''}">
    {#if isDisabled}
        Disabled
    {:else}
        Click me
    {/if}
</button>

<style>
   .button {
        background-color: blue;
        color: white;
        padding: 10px 20px;
    }
   .button--disabled {
        background-color: gray;
        cursor: not - allowed;
    }
</style>

这里,button 是块(Block),button--disabled 是带有修饰符(Modifier)的块,表示按钮的禁用状态。这种命名方式使得样式规则清晰,易于理解和修改。

(二)全局样式

  1. 谨慎使用全局样式:虽然 Svelte 强调组件级别的样式,但在某些情况下,可能需要定义全局样式,如设置全局字体、颜色主题等。在定义全局样式时,要谨慎操作,避免对组件样式产生意外影响。通常,可以在项目的入口文件(如 main.js)中引入全局样式文件。
<!-- main.js -->
import './global.css';
import App from './App.svelte';

const app = new App({
    target: document.body
});

export default app;
  1. 全局样式的命名约定:为了避免与组件内样式冲突,全局样式的类名可以采用特定的命名约定。例如,可以在类名前加上 global - 前缀。
/* global.css */
.global - body {
    font - family: Arial, sans - serif;
    color: #333;
}

这样在使用全局样式时,就可以明确区分全局样式和组件内样式。

四、代码注释

(一)单行注释

  1. 解释复杂逻辑:在代码中,当遇到复杂的逻辑时,使用单行注释来解释其目的和实现方式。例如,在一个计算复杂数学公式的函数中:
<script>
    function calculateComplexFormula(x, y) {
        // 这里的公式是根据特定的业务需求推导出来的,用于计算 x 和 y 的复杂关系
        let result = Math.pow(x, 2) + Math.sqrt(y) - (x * y);
        return result;
    }
</script>
  1. 临时注释代码:有时,我们可能需要临时注释掉一些代码,比如在调试或者暂时不需要某些功能时。使用单行注释来注释掉这些代码。
<script>
    let num = 10;
    // let anotherNum = 20; // 暂时注释掉这行代码,因为还不需要使用 anotherNum
    let sum = num;
</script>

(二)多行注释

  1. 函数和组件的文档注释:对于函数和组件,使用多行注释来编写文档注释,描述其功能、参数和返回值。例如,在一个组件中:
<!-- MyComponent.svelte -->
<script>
    /**
     * 该组件用于展示用户的基本信息
     * @param {Object} user - 用户对象,包含 name 和 age 属性
     * @returns {void} 无返回值,直接在 DOM 中渲染用户信息
     */
    let user = { name: 'Alice', age: 28 };
</script>

<div>
    <h2>{user.name}</h2>
    <p>Age: {user.age}</p>
</div>
  1. 模块级别的注释:在一个模块(如一个 JavaScript 文件)中,可以使用多行注释来描述模块的功能、依赖关系等。例如,在一个工具函数模块中:
// utils.js
/**
 * 这个模块包含了一些常用的工具函数
 * 依赖:无
 * 作者:[你的名字]
 * 版本:1.0.0
 */

export function addNumbers(a, b) {
    return a + b;
}

export function multiplyNumbers(a, b) {
    return a * b;
}

五、代码格式化

(一)缩进

  1. 使用 4 个空格缩进:在 Svelte 代码中,推荐使用 4 个空格进行缩进,这有助于保持代码的整齐和可读性。例如,在一个组件的模板中:
<script>
    let items = [1, 2, 3];
</script>

<ul>
    {#each items as item}
        <li>{item}</li>
    {/each}
</ul>

使用 4 个空格缩进使得代码块的层次结构一目了然,特别是在嵌套结构较多的情况下。

  1. 保持缩进一致性:在整个项目中,要保持缩进的一致性。不要在一些地方使用空格缩进,而在另一些地方使用制表符(Tab)缩进。可以通过配置代码编辑器,使其默认使用 4 个空格进行缩进。

(二)代码换行

  1. 长行换行:当代码行过长时,应该进行换行,以提高可读性。例如,在一个包含复杂逻辑的表达式中:
<script>
    let longExpression = (a * b + c / d) * (e - f) + (g + h) * (i / j - k)
        + (l * m + n / o);
</script>

在这里,将长表达式换行,使每行代码都不会过长,便于阅读和理解。

  1. 合理断行位置:换行的位置应该选择在运算符之后,这样可以清楚地表明后续代码与前面代码的逻辑关系。例如,不要将运算符放在新行的开头,像下面这样是不好的:
<script>
    let badLineBreak = a 
        * b;
</script>

而应该这样换行:

<script>
    let goodLineBreak = a *
        b;
</script>

(三)空白字符

  1. 适当使用空白字符:在代码中,适当使用空白字符(空格、空行等)可以提高代码的可读性。例如,在函数定义中,参数之间可以使用空格分隔,函数体与定义之间可以空一行:
<script>
    function addNumbers(a, b) {

        return a + b;
    }
</script>
  1. 避免过多空白字符:虽然适当的空白字符有助于阅读,但过多的空白字符会使代码显得松散,降低代码的紧凑性和可读性。例如,不要在一行代码中插入过多不必要的空格:
<script>
    let tooManySpaces     =   a    +   b;
</script>

而应该保持简洁:

<script>
    let properSpacing = a + b;
</script>

六、数据绑定与响应式编程

(一)双向数据绑定

  1. 基本原理:Svelte 的双向数据绑定是其强大功能之一,它允许在模板和脚本之间自动同步数据。例如,在一个输入框和一个变量之间实现双向绑定:
<script>
    let inputValue = '';
</script>

<input type="text" bind:value={inputValue}>
<p>You entered: {inputValue}</p>

当用户在输入框中输入内容时,inputValue 变量会自动更新;同时,如果通过脚本修改 inputValue 变量的值,输入框中的内容也会相应改变。

  1. 双向绑定的使用场景:双向绑定在表单处理、用户交互等场景中非常有用。比如,在一个用户设置页面,用户可以通过输入框、下拉框等组件修改设置,这些设置会实时反映到应用的状态中。
<script>
    let theme = 'light';
</script>

<select bind:value={theme}>
    <option value="light">Light theme</option>
    <option value="dark">Dark theme</option>
</select>
<p>Current theme: {theme}</p>

这里,用户选择不同的主题选项,theme 变量会自动更新,应用可以根据 theme 的值来切换页面的样式。

(二)响应式声明

  1. $: 符号的使用:在 Svelte 中,使用 $: 符号来创建响应式声明。当响应式声明依赖的变量发生变化时,声明的代码块会自动重新执行。例如:
<script>
    let num1 = 10;
    let num2 = 20;
    $: sum = num1 + num2;
</script>

<p>The sum of {num1} and {num2} is {sum}</p>

num1num2 的值发生变化时,sum 会自动重新计算并更新显示。

  1. 复杂响应式逻辑:响应式声明不仅可以用于简单的计算,还可以处理复杂的逻辑。例如,在一个根据用户输入动态生成列表的场景中:
<script>
    let inputText = '';
    let items = [];
    $: {
        items = inputText.split(' ').filter((word) => word.length > 0);
    }
</script>

<input type="text" bind:value={inputText}>
<ul>
    {#each items as item}
        <li>{item}</li>
    {/each}
</ul>

inputText 发生变化时,响应式代码块会重新执行,将输入的文本分割成单词并过滤掉空字符串,然后更新 items 列表,从而在模板中重新渲染列表。

七、事件处理规范

(一)事件绑定语法

  1. 基本事件绑定:在 Svelte 中,使用 on:eventName 语法来绑定事件。例如,绑定一个按钮的点击事件:
<script>
    function handleClick() {
        console.log('Button clicked');
    }
</script>

<button on:click={handleClick}>Click me</button>
  1. 事件修饰符:Svelte 支持事件修饰符,用于对事件进行一些特殊处理。比如,preventDefault 修饰符可以阻止事件的默认行为。在一个表单提交事件中:
<script>
    function handleSubmit() {
        console.log('Form submitted');
    }
</script>

<form on:submit|preventDefault={handleSubmit}>
    <input type="submit" value="Submit">
</form>

这里,preventDefault 修饰符阻止了表单提交时页面的默认刷新行为。

(二)事件处理函数的逻辑

  1. 保持函数职责单一:事件处理函数应该保持职责单一,只处理与该事件相关的逻辑。例如,在一个购物车添加商品的按钮点击事件中,事件处理函数只负责将商品添加到购物车,而不应该同时处理购物车总价计算等其他逻辑。
<script>
    let cart = [];
    let newProduct = { name: 'Product 1', price: 10 };

    function addToCart() {
        cart.push(newProduct);
    }
</script>

<button on:click={addToCart}>Add to cart</button>
  1. 避免复杂逻辑:尽量避免在事件处理函数中编写过于复杂的逻辑。如果逻辑较为复杂,可以将其拆分到多个函数中,或者使用辅助函数来处理。例如,在一个处理用户登录成功后的一系列操作时:
<script>
    function handleLoginSuccess() {
        updateUserProfile();
        redirectToDashboard();
    }

    function updateUserProfile() {
        // 更新用户资料逻辑
    }

    function redirectToDashboard() {
        // 重定向到仪表盘逻辑
    }
</script>

这样,handleLoginSuccess 函数只负责调用其他函数,使代码逻辑更加清晰。

八、代码复用

(一)组件复用

  1. 创建可复用组件:在 Svelte 中,通过将一些通用的功能封装成组件,可以实现组件的复用。例如,创建一个通用的按钮组件:
<!-- Button.svelte -->
<script>
    let text = 'Click me';
    let onClick = () => {};
</script>

<button on:click={onClick}>{text}</button>

然后在其他组件中可以引入并使用这个按钮组件:

<!-- AnotherComponent.svelte -->
<script>
    import Button from './Button.svelte';

    function handleCustomClick() {
        console.log('Custom click');
    }
</script>

<Button text="Custom button" on:click={handleCustomClick} />
  1. 通过 props 定制组件:为了使组件更具通用性,可以通过 props 来传递不同的值,从而定制组件的行为和外观。例如,在上述按钮组件中,可以通过 props 传递按钮的颜色:
<!-- Button.svelte -->
<script>
    let text = 'Click me';
    let onClick = () => {};
    let color = 'blue';
</script>

<button style="background - color: {color}" on:click={onClick}>{text}</button>

在使用该按钮组件时:

<!-- AnotherComponent.svelte -->
<script>
    import Button from './Button.svelte';

    function handleCustomClick() {
        console.log('Custom click');
    }
</script>

<Button text="Custom button" on:click={handleCustomClick} color="green" />

(二)逻辑复用

  1. 使用 JavaScript 模块:对于一些通用的逻辑,可以将其封装成 JavaScript 模块,然后在不同的组件中引入使用。例如,创建一个包含一些数学计算函数的模块:
// mathUtils.js
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

在 Svelte 组件中引入并使用这些函数:

<script>
    import { add, multiply } from './mathUtils.js';

    let num1 = 5;
    let num2 = 3;
    let sum = add(num1, num2);
    let product = multiply(num1, num2);
</script>

<p>The sum of {num1} and {num2} is {sum}</p>
<p>The product of {num1} and {num2} is {product}</p>
  1. 使用 Svelte store:Svelte store 可以用于共享状态,实现逻辑复用。例如,创建一个共享的计数器 store:
// counterStore.js
import { writable } from'svelte/store';

export const counter = writable(0);

在不同的组件中可以引入并使用这个 store:

<!-- ComponentA.svelte -->
<script>
    import { counter } from './counterStore.js';
    function increment() {
        counter.update((n) => n + 1);
    }
</script>

<button on:click={increment}>Increment in ComponentA</button>
<p>Counter value: {$counter}</p>
<!-- ComponentB.svelte -->
<script>
    import { counter } from './counterStore.js';
    function decrement() {
        counter.update((n) => n - 1);
    }
</script>

<button on:click={decrement}>Decrement in ComponentB</button>
<p>Counter value: {$counter}</p>

这样,不同组件可以共享和操作同一个计数器状态。