欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

2021 年前端开发人员需要了解的 JS/React 规范

最编程 2024-03-08 10:09:17
...

规范: 导入模块的顺序

以有组织的方式引入 ES6 模块将节省你查找任何找不到或不需要模块的时间

之前

import { PageHeaderWrapper } from "@ant-design/pro-layout";
import { CustomFormHOC } from "@/components/FormWidget";
import { Form, Card, Button, Divider, message, Alert } from "antd";
import { connect } from "dva";
import PropTypes from "prop-types";
import React, { Component } from "react";
import TransportInfo from "./TransportInfo";
import RecordInfo from "./RecordInfo";
import router from "umi/router";
import styles from "./index.less";

之后

// node_modules
import React, { Component } from "react";
import { Form, Card, Button, Divider, message, Alert } from "antd";
import { connect } from "dva";
import router from "umi/router";
import PropTypes from "prop-types";
import { PageHeaderWrapper } from "@ant-design/pro-layout";
// 项目公共模块
import { CustomFormHOC } from "@/components/FormWidget";
// 当前业务耦合模块
import TransportInfo from "./TransportInfo";
import RecordInfo from "./RecordInfo";
// 样式文件
import styles from "./index.less";

之前的引入是无序的,一个文件可能会很乱,但是当你打开大量文件时候,尝试找到一个特定的包真的很难。

使用之后的方式对导入的包进行分组,通过空格行分割每个模块。又因为文件将保持一致,就可以删除注释了。

规范: 尽可能使用解构

另外一个重要的事情就是防止不必要的嵌套和重复(将对象的属性值保存为局部变量)

  • 对象成员嵌套越深,读取速度也就越慢。所以好的经验法则是:如果在函数中需要多次读取一个对象属性,最佳做法是将该属性值保存在局部变量中,避免多次查找带来的性能开销(对象变量避免嵌套过深)
  • 函数参数越少越好,如果参数超过两个,要使用 ES6 的解构语法,这样就不用考虑参数的顺序了
  • 使用参数默认值 替代使用条件语句进行赋值

在大多数情况下,将大大提升可读性。

之前

const Page=(deliveryCompany,carrierName,driverInfo,driverInfo)=>{
  return(
                <Descriptions.Item label="供应商">{deliveryCompany || '未知'}</Descriptions.Item>
                <Descriptions.Item label="承运商">{carrierName || '未知'}</Descriptions.Item>
                <Descriptions.Item label="司机">{driverInfo.driver || '未知'}</Descriptions.Item>
                <Descriptions.Item label="联系方式">{driverInfo.contact || '未知'}</Descriptions.Item>
  );
}

之后

const Page=dataDetail=>{
  const {
    deliveryCompany = '未知',
    carrierName = '未知',
    driverInfo = { driver: '未知', contact: '未知' },
  } = props;

  const { driver, contact } = driverInfo;

  return(
                <Descriptions.Item label="供应商">{deliveryCompany}</Descriptions.Item>
                <Descriptions.Item label="承运商">{carrierName}</Descriptions.Item>
                <Descriptions.Item label="司机">{driver}</Descriptions.Item>
                <Descriptions.Item label="联系方式">{contact}</Descriptions.Item>
  );
}

规范: 变量和方法的命名约定

关于代码,有一点很重要,就是要知道一个方法将返回什么,或者通过变量名轻松理解变量的含义(变量语义化),比如

  • JS 采用 Camel Case 小驼峰式命名
  • 避免名称冗余,保证语义明确
  • 每个常量都需要有意义的变量名(不然看代码的人不知道这个常量表示什么意思)
动词 含义
can 判断是否可执行某个动作
has 判断是否含有某个值
is 判断是否为某个值
get 获取某个值
set 设置某个值

之前

User.cat = true;
User.admin = true;

function NewUser(age) {
  User.age = 150 - age;
  return User;
}

function add_photo(photo) {
  User.photo = photo;
}

之后

let user = {};

user.hasCat = true;
User.isAdmin = true;

const maxAge = 150;

function getUser(age) {
  User.age = maxAge - age;
  return User;
}

function setUserPhoto(photo) {
  User.photo = photo;
}

之后展示了如何在命名变量和方法保持一致性,在以下方面保持一致:

  • 对于布尔类型使用:is, has,should 做前缀
  • 对于方法使用 get/set 做前缀
  • 变量和方法都使用驼峰命名
  • 使用 ES6 的 const 定义常量

之后,组件为注入一些公共变量做准备,比如 style, className, key 等等,使用展开操作,将一组公共属性传入容器。

规范: 删除弃用代码

很多时候有些代码已经没有用了,但是没有及时去删除,这样导致代码里面包括很多注释的代码块,好的习惯是提交代码前记得删除已经确认弃用的代码,例如:一些调试的console语句、无用的弃用代码。 版本控制系统的存在是有原因的。如果想找旧代码就到 Git history 去找吧。

之前

doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();

之后

doStuff();

规范: 保持必要的注释

代码注释不是越多越好,保持必要的业务逻辑注释,至于函数的用途、代码逻辑等,要通过语义化的命令、简单明了的代码逻辑,来让阅读代码的人快速看懂。

规范: 使用 Async/Await 代替 Promise 和回调函数

Promise 是较回调而言更好的一种选择,但 ES7 中的 async 和 await 更胜过 Promise

在能使用 ES7 特性的情况下可以尽量使用他们替代 Promise

之前

require("request-promise")
  .get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(function (response) {
    return require("fs-promise").writeFile("article.html", response);
  })
  .then(function () {
    console.log("File written");
  })
  .catch(function (err) {
    console.error(err);
  });

之后

async function getCleanCodeArticle() {
  try {
    const request = await require("request-promise");
    const response = await request.get(
      "https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
    );
    const fileHandle = await require("fs-promise");

    await fileHandle.writeFile("article.html", response);
    console.log("File written");
  } catch (err) {
    console.log(err);
  }
}

规范: 如何捕获错误

错误抛出是个好东西!这使得你能够成功定位运行状态中的程序产生错误的位置。

使用 try catch

对捕获的错误不做任何处理是没有意义的。

代码中 try/catch 的意味着你认为这里可能出现一些错误,你应该对这些可能的错误存在相应的处理方案(前端监控方案之一)。

之前

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

之后

try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}

使用 Promise 的 catch

之前

getdata()
  .then((data) => {
    functionThatMightThrow(data);
  })
  .catch((error) => {
    console.log(error);
  });

之后

getdata()
  .then((data) => {
    functionThatMightThrow(data);
  })
  .catch((error) => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });

规范: React 相关

一些非常基础的规范这里就不说了, 说一些比基础高级一点的 ????

  • 推荐使用函数式+Hooks 编写代码

遵守 Hooks 规则

不要在循环、条件和嵌套函数内调用 Hooks。当你想有条件地使用某些 Hooks 时,请在这些 Hooks 中写入条件。

错误代码

if (name !== "") {
  useEffect(function persistForm() {
    localStorage.setItem("formData", name);
  });
}

应该这样做

useEffect(function persistForm() {
  if (name !== "") {
    localStorage.setItem("formData", name);
  }
});

这条规则能确保每次渲染组件时都以相同的顺序调用 Hooks。这样一来,React 就能在多个 useState 和 useEffect 调用之间正确保留 Hooks 的状态。

使用 ESLint 的 React Hooks 插件

React 团队还创建了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,以帮助开发人员在自己的项目中以正确的方式编写 React Hooks。这个插件能够帮助你在尝试运行应用程序之前捕获并修复 Hooks 错误。

它有两条简单的规则:

  • react-hooks/rules-of-hooks
  • react-hooks/exhaustive-deps

第一条规则只是强制你的代码符合我在第一个技巧中说明的 Hooks 规则。第二个规则,exhaustive-deps 用于实施 useEffect 的规则:effect 函数中引用的每个值也应出现在依赖项数组中。 例如,下面这个 userInfo 组件会触发 exhaustive-deps 警告,因为 userId 变量在 useEffect 内部被引用,但未在依赖项数组中传递:

function UserInfo({ userId }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    getUser(userId).then((user) => setUser(user));
  }, []); // no userId here
  return <div>User detail:</div>;
}

以正确的顺序创建函数组件

建议先使用 useState Hook 声明状态变量,然后使用 useEffect Hook 编写订阅,接着编写与组件作业相关的其他函数。

最后,你得返回要由浏览器渲染的元素:

function App() {
  const [user, setUser] = useState(null);
  const [name, setName] = useState("");
  useEffect(() => {
    console.log("component is mounted");
  }, []);
  return <h1>React component order</h1>;
}

useState 的用法可以和类组件的状态完全一致,不只用于单个值

许多 useState 示例会向你展示如何通过声明多个变量来声明多个状态:

const [name, setName] = useState("John Doe");
const [email, setEmail] = useState("johndoe@email.com");
const [age, setAge] = useState(28);

但是 useState 实际上既可以处理数组也可以处理对象。你依旧可以将相关数据分组为一个状态变量,如以下示例所示:

const [user, setUser] = useState({
  name: "John Doe",
  email: "john@email.com",
  age: 28,
});

这里有一个警告。使用 useState 的更新函数更新状态时,以前的状态会替换为新状态。这与类组件的 this.setState 不同,后者的新类中,新状态会与旧状态合并:

const [user, setUser] = useState({
  name: "John",
  email: "john@email.com",
  age: 28,
});
setUser({ name: "Nathan" });
// result { name: 'Nathan' }

为了保留以前的状态,你需要创建将当前状态值传递到自身中的回调函数来手动合并它。由于上面的示例已将 user 变量分配为状态值,因此可以将其传递给 setUser 函数,如下所示:

setUser((user) = > ({ ...user, name: 'Nathan' }));
// result is { name:'Nathan', email: 'john@email.com', age: 28 }

根据数据在应用程序生命周期中的变化情况,建议在各个值彼此独立时将状态拆分为多个变量。 但是对于某些情况,例如构建一个简单的表单,最好将状态分组在一起,以便更轻松地处理更改和提交数据。

简而言之,你需要在多个 useState 调用和单个 useState 调用之间保持平衡。

使用自定义 Hooks 共享应用程序逻辑

在构建应用程序时,你会注意到一些应用程序逻辑会在许多组件中一次又一次地使用。 随着 React Hooks 的发布,你可以将组件的逻辑提取到可重用的函数中作为自定义 Hooks,比如"非著名"阿里开源库 ahooks

使用 useContext 避免 prop drilling

prop-drilling 是 React 应用程序中的常见问题,指的是将数据从一个父组件向下传递,经过各层组,直到到达指定的子组件,而其他嵌套组件实际上并不需要它们。

React Context 是一项功能,它提供了一种通过组件树向下传递数据的方法,这种方法无需在组件之间手动传 props。父组件中定义的 React Context 的值可由其子级通过 useContext Hook 访问。

1. 组件声明

组件名称和定义该组件的文件名称建议要保持一致

之前

import FooterComponent from "./FooterComponent/index";

之后

import Footer from "./Footer/index";

2. React 中的命名

  • 组件名称: 推荐使用大驼峰命名;
  • 属性名称: React DOM 使用小驼峰命令来定义属性的名称,而不使用 HTML 属性名称的命名约定;
  • style 样式属性: 采用小驼峰命名属性的 JavaScript 对象;
// 组件名称
MyComponent;
// 属性名称
onClick;
// 样式属性
backgroundColor;

3.  对齐

// 推荐
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
/>

// 如果组件的属性可以放在一行(一个属性时)就保持在当前一行中
<Foo bar="bar" />

// 多行属性采用缩进
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
>
  <Quux />
</Foo>

// 不推荐
<Foo superLongParam="bar"
     anotherSuperLongParam="baz" />

4. 引号

JSX 的属性都采用双引号,其他的 JS 都使用单引号 ,因为 JSX 属性 不能包含转义的引号, 所以当输入 "don't" 这类的缩写的时候用双引号会更方便

之前

<Foo bar='bar' />

<Foo style={{ left: "20px" }} />

之后

<Foo bar="bar" />

<Foo style={{ left: '20px' }} />

4.  为你的组件接收公共变量做好准备

之前

const UserInfo = (props) => {
  const { name, sex, age } = props;
  return (
    <div>
      <div>姓名:{name}</div>
      <div>性别:{sex}</div>
      <div>年龄:{age}</div>
    </div>
  );
};

之后

const UserInfo = (props) => {
  const { name, sex, age, ...rest } = props;
  return (
    <div {...rest}>
      <div>姓名:{name}</div>
      <div>性别:{sex}</div>
      <div>年龄:{age}</div>
    </div>
  );
};

7.  组件遵循单一职责原则

组件遵循单一职责原则(Single Responsibility Principle)可以让你轻松创建和贡献代码,并保持代码库的整洁。

即容器组件与傻瓜组件。

  • 容器组件负责数据的请求与获取,props/state 的更新。
  • 傻瓜组件只负责接收 props,抛出事件

之前

import React, { useState, useEffect } from "react";
import fetch from "isomorphic-fetch";

const UserInfo = (props) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function getUser() {
      const user = await fetch("/userAppi");

      if (user) {
        setUser(user);
      }
    }
    getUser();
  }, []);

  const { name, age } = user;
  return (
    <div>
      <div>{name}</div>
      <div>{age}</div>
    </div>
  );
};

之后

import React, { useState, useEffect } from "react";
import { fetchUser } from "./api";

const UserInfo = (props) => {
  const { user } = props;

  const { name, age } = user;
  return (
    <div>
      <div>{name}</div>
      <div>{age}</div>
    </div>
  );
};

const UserInfoPage = (props) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function getUser() {
      const user = await fetchUser("/userAppi");

      if (user) {
        setUser(user);
      }
    }
    getUser();
  }, []);

  return <UserInfo user={user} />;
};

附加:如果你正在使用类型检查器,请让它发挥作用。 如果你的团队选择使用类型检查器,那么使用严格模式很重要,以确保它能发挥作用,来达到使用它的目的。

interface UserInfoInterface {
  name: string;
  age: number;
}

const UserInfo = (props: UserInfoInterface) => {
  const { user } = props;

  const { name, age } = user;
  return (
    <div>
      <div>{name}</div>
      <div>{age}</div>
    </div>
  );
};

参考文档

  1. [译]提升你 react 和 js 编码的 5 个技巧
  2. 团队 React 代码规范制定
  3. 前端团队代码评审 CheckList 清单
  4. 京东凹凸实验室代码规范

最后

在日常工作中你还使用哪些 JavaScript规范呢?欢迎在评论区留下的你的见解!

觉得有收获的朋友欢迎点赞关注一波!

往期文章

react 构建系列

  1. 企业级前端开发规范如何搭建 ????
  2. 「React Build」之集成 Webpack5/React17
  3. 「React Build」之集成 CSS/Less/Sass/Antd
  4. 「React Build」之集成图片/字体
  5. 「React Build」之集成 Redux/Typescript
  6. 「React Build」之使用 Redux-thunk 实现 Redux 异步操作
  7. 「React Build」之集成 React-Router/Antd Menu 实现路由权限