#####编写目的
- 为规范软件开发人员的代码编写提供参考依据和统一标准
#####文档约定
- [强制]表示必须按要求执行
- [推荐]表示建议按要求执行
- [推荐偏强制]自己理解执行
#####参考文献
#####基本规范
- [强制]每个文件只包含一个React组件
- [强制]React Native 和 React 都要尽可能的使用ES6语法
- [强制]console在调试时写、调试完立即删除
- [强制]不允许未经定义的常量直接出现在代码中
- [强制]项目目录应按功能进行创建及维护
- [建议偏强制]两段功能或业务相同或类似的代码不应该出现两次
- [建议]使用第三方插件时应仔细阅读官方文档的 Readme.md、issue、最新提交代码的时间、充分了解可能会出现的问题及解决办法、节省调试时间、降低开发难度
- [建议]将接口地址按功能划分归类分开维护、后期接口升级等便于维护
- [建议]不要使用一个常量类维护所有常量、应该按常量功能进行归类、分开维护; 大而全的常量类、非得使用查找功能才能定位到修改的常量、不利于理解和维护
#####目录规范
- 以下目录结构示例中只展示js与静态资源、不包含原生代码
├── index.js
├── App.js
└── js
├── component------------可复用的组件(非完整页面)
├── page-----------------完整页面
├── config---------------配置项(常量、接口地址、路由、多语言化等预置数据)
├── util-----------------工具类(非UI组件)
├── style----------------全局样式
└── image----------------图片
- 在component和page目录中、可能还有一些内聚度较高的模块再建目录
page/component
├── HomeView.component.js
├── HomeView.style.js
└── MovieView
├── MovieList.component.js
├── MovieList.style.js
├── MovieCell.component.js
├── MovieCell.style.js
├── MovieView.component.js
└── MovieView.style.js
- 通用的组件放在Component文件夹
- 调用原生android/ios都写在同一个.js文件内
#####代码顺序 ######import引用顺序
React
ReactNative
Redux
ReactRedux
第三方库
自定义组件
图片
公共样式
业务组件及其样式
Actions
- [强制]import顺序一般如下并且分组; 对组件引用、变量引用、需遵从以下方式
import React, {Component} from 'react';
import{
View,
Text,
TouchableHighlight,
Image,
StyleSheet,
InteractionManager,
} from 'react-native';
//from react、react-native优先
//from npm库其次
import { connect } from 'react-redux';
//from 项目内组件其次
import LoadingAndTime from '../component/LoadingAndTime';
import { performLoginAction } from '../action/LoginAction'
import {encode} from '../common/Base64';
//变量初始化、常量初始化最后
let screenWidth = Dimensions.get('window').width;
let screenHeight = Dimensions.get('window').height;
let typeCode = Platform.OS == 'android' ? 'android-phone' : 'ios-phone'
let selectColor=Platform.OS=='android'?null:'white'
- [推荐]对组件引用、变量初始化等、在整个页面或组件内未使用、因去除相关代码
- [推荐]某些全局变量请不要使用global、需新建文件进行导出引用; NetUtil.get(global.url + “”)
- [推荐]render() 函数代码过长时、请适当进行拆分、拆分为”页面内组件“提高可读性; render()函数代码行请勿超过八十行、超过之后、请自行进行拆
######方法函数顺序
- 方法的顺序如下
getDefaultProps
getInitialState
componentWillMount
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
other method ...
render
#####命名规范
- [强制]文件夹命名使用大驼峰命名法 比如: Home、User、Set
- [强制]文件命名要使用大驼峰命名法 比如: HomePage.js、UserPage.js
- [强制]React Native 中如果仅有 android/ios 其中一端的功能、在文件的尾加 .android/.ios; 比如 KeyBoard.android.js、KeyBoard.ios.js
- [强制]杜绝 1、2、3、one、two、three 之类名称; 用功能或者是模块来命名
- [强制]函数命名、事件处理函数的命名采: 用 “handle” + “EventName” 的方式来命名; 事件函数作为属性时的命名: on +EventName 与 handle 对应 <Component onClick={this.handleClick} />
- [强制]代码中用于页面展示处理UI的组件、命名以Page结尾、自定义组件命名以Component结尾 如: LoginPage、ButtonComponent
- [强制]变量命名定义规则如
let sUserName = 'xxx'; // 字符串 s开头
let nAge = 20; // 数字 n开头
let isRequestSuccess = false; let hasProperty = false; // Boolean 使用 is、has等
let uPaperName = undefined; // undefined u开头
let aMyArray = []; // 数组 a开头
let oMyObject = {}; // 对象 o开头
- [推荐]命名取词: 变量名应当使用名词、boolean类型的应当使用 is、has 等开头表示其类型、函数名应当用动宾短语、类名应当用名词
#####结构规范 ######缩进
- [强制]使用 4 个空格做为一个缩进层级; 不允许使用 2 个空格 或 tab 字符; switch 下的 case 和 default 必须增加一个缩进层级
// good
switch (variable) {
case '1':
// do...
break;
case '2':
// do...
break;
default:
// do...
}
// bad
switch (variable) {
case '1':
// do...
break;
case '2':
// do...
break;
default:
// do...
}
######空格
- [强制]二元运算符两侧必须有一个空格; 一元运算符与操作对象之间不允许有空格
let a = !arr.length;
a++;
a = b + c;
- [强制]用作代码块起始的左花括号 { 前必须有一个空格
// good
if (condition) {
// do...
}
while (condition) {
// do...
}
function funcName() {
// do...
}
// bad
if (condition){
// do...
}
while (condition){
// do...
}
function funcName(){
// do...
}
- [强制]if / else / for / while / function / switch / do / try / catch / finally 关键字后、必须有一个空格
// good
if (condition) {
}
while (condition) {
}
(function () {
})();
// bad
if(condition) {
}
while(condition) {
}
(function() {
})();
- [强制]在对象创建时、属性中的、 : 之后必须有空格、 : 之前不允许有空格
// good
var obj = {
a: 1,
b: 2,
c: 3
};
// bad
var obj = {
a : 1,
b:2,
c :3
};
- [强制]函数声明、具名函数表达式、函数调用中; 函数名和 ( 之间不允许有空格
// good
function funcName() {
}
var funcName = function funcName() {
};
funcName();
// bad
function funcName () {
}
var funcName = function funcName () {
};
funcName ();
- [强制] , 和 ; 前不允许有空格; 如果不位于行尾、 , 和 ; 后必须跟一个空格
// good
callFunc(a, b);
// bad
callFunc(a , b) ;
- [强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中; () 和 [] 内紧贴括号部分不允许有空格
// good
callFunc(param1, param2, param3);
save(this.list[this.indexes[i]]);
needIncream && (variable += increament);
if (num > list.length) {
}
while (len--) {
}
// bad
callFunc( param1, param2, param3 );
save( this.list[ this.indexes[ i ] ] );
needIncreament && ( variable += increament );
if ( num > list.length ) {
}
while ( len-- ) {
}
- [强制] 单行声明的数组与对象、如果包含元素,{} 和 [] 内紧贴括号部分不允许包含空格; 声明包含元素的数组与对象、只有当内部元素的形式较为简单时、才允许写在一行; 元素复杂的情况、还是应该换行书写
// good
var arr1 = [];
var arr2 = [1, 2, 3];
var obj1 = {};
var obj2 = {name: 'obj'};
var obj3 = {
name: 'obj',
age: 20,
sex: 1
};
// bad
var arr1 = [ ];
var arr2 = [ 1, 2, 3 ];
var obj1 = { };
var obj2 = { name: 'obj' };
var obj3 = {name: 'obj', age: 20, sex: 1};
- [强制]行尾不得有多余的空格
######换行
- [强制]每个独立语句结束后必须换行
- [强制]每行不得超过 120 个字符; 超长的不可分割的代码允许例外、比如复杂的正则表达式、长字符串不在例外之列
- [强制]运算符处换行时、运算符必须在新行的行首
// good
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
|| user.hasAuthority('delete-admin')
) {
// Code
}
var result = number1 + number2 + number3
+ number4 + number5;
// bad
if (user.isAuthenticated() &&
user.isInRole('admin') &&
user.hasAuthority('add-admin') ||
user.hasAuthority('delete-admin')) {
// Code
}
var result = number1 + number2 + number3 +
number4 + number5;
- [强制]在函数声明、函数表达式、函数调用、对象创建、数组创建、for 语句等场景中,不允许在 , 或 ; 前换行
// good
var obj = {
a: 1,
b: 2,
c: 3
};
foo(
aVeryVeryLongArgument,
anotherVeryLongArgument,
callback
);
// bad
var obj = {
a: 1
, b: 2
, c: 3
};
foo(
aVeryVeryLongArgument
, anotherVeryLongArgument
, callback
);
- [建议] 不同行为或逻辑的语句集、使用空行隔开、更易阅读
// 仅为按逻辑换行的示例,不代表setStyle的最优实现
function setStyle(element, property, value) {
if (element == null) {
return;
}
element.style[property] = value;
}
- [建议]在语句的行长度超过 120 时; 根据逻辑条件合理缩进
// 较复杂的逻辑条件组合、将每个条件独立一行、逻辑运算符放置在行首进行分隔、或将部分逻辑按逻辑组合进行分隔
// 建议最终将右括号 ) 与左大括号 { 放在独立一行、保证与 `if` 内语句块能容易视觉辨识
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
|| user.hasAuthority('delete-admin')
) {
// Code
}
// 按一定长度截断字符串、并使用 + 运算符进行连接
// 分隔字符串尽量按语义进行、如不要在一个完整的名词中间断开
// 特别的、对于 HTML 片段的拼接、通过缩进、保持和 HTML 相同的结构
var html = '' // 此处用一个空字符串、以便整个 HTML 片段都在新行严格对齐
+ '<article>'
+ '<h1>Title here</h1>'
+ '<p>This is a paragraph</p>'
+ '<footer>Complete</footer>'
+ '</article>';
// 也可使用数组来进行拼接、相对 `+` 更容易调整缩进
var html = [
'<article>',
'<h1>Title here</h1>',
'<p>This is a paragraph</p>',
'<footer>Complete</footer>',
'</article>'
];
html = html.join('');
// 当参数过多时、将每个参数独立写在一行上、并将结束的右括号 ) 独立一行
// 所有参数必须增加一个缩进
foo(
aVeryVeryLongArgument,
anotherVeryLongArgument,
callback
);
// 也可以按逻辑对参数进行组合
// 最经典的是 baidu.format 函数、调用时将参数分为“模板”和“数据”两块
baidu.format(
dateFormatTemplate,
year, month, date, hour, minute, second
);
// 当函数调用时、如果有一个或以上参数跨越多行、应当每一个参数独立一行
// 这通常出现在匿名函数或者对象初始化等作为参数时; 如 `setTimeout` 函数等。
setTimeout(
function () {
alert('hello');
},
200
);
order.data.read(
'id=' + me.model.id,
function (data) {
me.attchToModel(data.result);
callback();
},
300
);
// 链式调用较长时采用缩进进行调整
$('#items')
.find('.selected')
.highlight()
.end();
// 三元运算符由3部分组成、因此其换行应当根据每个部分的长度不同、形成不同的情况
var result = thisIsAVeryVeryLongCondition
? resultA : resultB;
var result = condition
? thisIsAVeryVeryLongResult
: resultB;
// 数组和对象初始化的混用、严格按照每个对象的 `{` 和结束 `}` 在独立一行的风格书写
var array = [
{
// ...
},
{
// ...
}
];
######语句
- [强制]不得省略语句结束的分号
- [强制]在 if / else / for / do / while 语句中]、即使只有一行、也不得省略块 {...}
// good
if (condition) {
callFunc();
}
// bad
if (condition) callFunc();
if (condition)
callFunc();
- [强制]函数定义结束不允许添加分号
// good
function funcName() {
}
// bad
function funcName() {
};
// 如果是函数表达式、分号是不允许省略的
var funcName = function () {
};
#####注释规范 ######顶层文件注释
- 顶层注释用于告诉不熟悉这段代码的读者这个文件中包含哪些东西、应该提供文件的大体内容、它的作者、依赖关系、兼容性信息
/**
* @file LsOrRwPreview
* @date 2018/12/20 13:11
* @author Administrator
* @lastModify Administrator 2018/12/20 13:11
*/
######构造器函数
- @class 别名:@constructor
- 标明函数是一个构造器函数、意味着需要使用 new 关键字来返回一个实例、即使用 new 关键字实例化
/**
* Create new Point
* @class
*/
function Point {
// TODO:
}
let point = new Point();
######类的注释
- @classdesc
- 标签用于为类提供一个描述、这样和构造函数的描述区分开来; @classdesc标签应该与 @class (或@constructor)标签结合使用
// Example 1
/**
* Create new MyClass
* @class
* @classdesc This is MyClass description
* @params {string} className - 班级名称
* @params {string} classDescript - 班级描述
*/
function MyClass(className, classDescript) {
this.className = className;
this.classDescript = classDescript;
}
// Example 2
/**
* Create new MyClass
* @class
* @classdesc This is MyClass description
*/
class MyClass {
// TODO:
}
- 当使用 extends 关键字来扩展一个现有的类的时候; 可以使用 @augments (或 @extends) 标签
/**
* Class representing a dot.
* @class
* @classdesc This is HerClass description
* @extends MyClass
*/
class HerClass extends MyClass {
// TODO:
}
######函数注释
- @param 标签提供了对某个函数的参数的各项说明、包括参数名、参数数据类型、描述等; @param {变量类型} 变量名 - 变量描述 ex: @params {string | Number}
- @callback 描述一个回调函数
- @returns 描述一个函数的返回值; 语法和@param类似
/**
* Send a post Request
* @param {string} url - 请求地址
* @param {string} method - 请求方式
* @param {Object} body - 请求所需参数
* @callback successCallBack-requestSuccessCallBack - 请求成功的回调
* @callback errorCallBack-requestErrorCallBack - 请求失败的回调
* @returns {Promise.<*>}
*/
const requestUrl = async (url, method, body, successCallBack, errorCallBack) => {
reurn new Promise...
};
#####页面编写
- [强制]代码中初始化state因在constructor(props)函数中、而且尽量对每个变量进行注释
- [强制]代码中使用setState时、因注意异步可能导致的问题、尽量使用回调函数
this.setState({
// TODO:
},()=>{
// 执行setState后执行此函数
})
- [强制]代码中使用props时、需进行 propTypes 检测和 defaultProps 默认值初始化
static propTypes = {
color: PropTypes.string,
dotRadius: PropTypes.number,
size: PropTypes.number
};
static defaultProps = {
color: '#1e90ff',
dotRadius: 10,
size: 40
};
- [强制]代码中创建数组或对象使用以下方式
const user = {
name:'time',
sex:'男',
age:25,
};
const itemArray = ['0','1','2',3,{name:'25',age:'男'}];
- [强制]代码中函数绑定this、强制使用箭头函数; 注: 除组件原有方法、其他自定义函数命名时、需使用箭头函数
// 系统组件生命周期方法
constructor(props) {
super(props);
};
// 自定义方法
goMainPage=()=> {
console.log(this); // 对于箭头函数来说、并没有自己的 this 它的 this 将始终指向让它生效的对象、即它的外部调用者
};
- [强制]代码中一些网络数据初始化、配置信息、推荐在此生命周期进行初始化
componentWillMount
- [强制]代码中使用定时器或者DeviceEventEmitter、必须在组件卸载进行销毁或者清除
componentDidMount() {
// 注意addListener的key和emit的key保持一致
this.msgListener = DeviceEventEmitter.addListener('Msg',(listenerMsg) => {
this.setState({
listenerMsg:listenerMsg,
})
});
}
goMainPage=()=> {
this.timer = setTimeout(
() => { console.log('把一个定时器的引用挂在this上'); },
500
);
};
componentWillUnmount() {
// 此生命周期内、去掉监听和定时器
this.msgListener&&this.msgListener.remove();
// 如果存在this.timer、则使用clearTimeout清空、
// 如果你使用多个timer、那么用多个变量、或者用个数组来保存引用、然后逐个clear
this.timer && clearTimeout(this.timer);
};
- [推荐]关于平台区分这是创建一个 demo 默认的代码、要比三目运算符好一些
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
#####面向组件开发
- [推荐]开发react、react-native项目时应遵循此开发规范; 组件化的本质、实就是面向对象的设计思想; 组件化可以对应于 一个类、每个类都对外输出不变的接⼝、只要接⼝不变、类与类间的通讯可以根据接⼝进行、由此不同组件间的耦合度就降低了; 同时复杂的类可以通过若干个简单的类组合而成、这样一来、不但可以提高组件的重用性、同时通过组合的思想来设计复杂的控件、 也极大的降低了复杂控件的设计难度
如图所示
AssignNormalHomeWork.js,
AssignSpecialHomeWork.js,
FastAssignHomePage.js
都有选择日期、选择时间、选择班级的操作
我们将这三个view抽离成三个组件、界面无需关心组件如何实现、只需按照约定传递对应的值即可、选择后组件会将结果传递回去
如果以后需要改日期选择的样式、只需改动组件、而非像没有抽离成组件之前、改动三个界面甚至更多
这只是一个简单的组件化编程示例、可能还会遇到将一个复杂的组件抽离成多个子组件等等业务场景、大同小异
- 简述目录结构、如图所示、这是爱老师教师端作业版块下布的置作业模块、在assign目录下创建当前业务模块所需的component、page、util、这样目录结构一目、修改维护也更方便
#####样式规范
- [强制]当组件使用样式属性达到三个或者三个以上时、必须使用StyleSheet来创建样式属性并进行引用
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
marginTop:Common.scaleSize(10)
}
});
- [推荐]当使用单一属性、或者全局样式属性时、推荐使用公共样式类
// StyleCommon.js
export default {
topColor:{
backgroundColor: '#3A3D42',
},
mainView:{
backgroundColor: '#12141B',
},
}`在这里插入代码片`
- [推荐]当使用多个state或者props值时、推荐使用以下方式
const {size, dotRadius, color} = this.props;
const {maxNumber, minNumber} = this.state;
#####package.json
- [强制]在使用npm或者yarn获取资源时,必须在命令末尾添加--save; 使用此命令会把使用的第三方相关信息写入到package.json; 这样其他成员在下载或者更新代码后使用npm i、就可以下载最新的npm; 若不加 —save 、执行npm i的时候不会下载; 其他成员运行项目后在运行可能会报错、此时需要分析查看报错信息进行重新的npm install XX
- [推荐]使用git或者svn进行代码版本管理时、量不上传node_module文件; 使用package.json进行包管理、下载或更新代码后,只需要执行npm i; 当有修改npm包、建议进行版本管理、上传到私有的github仓库
- [强制]使用第三方或拉取新仓库时; 第一步使用npm i或者npm install; 说明: 检查版本是否存在冲突
- [推荐]在使用npm或者yarn获取资源时、推荐不在命令后添加 -g; 说明: 此命令可以让此资源包在根目录进行获取、不利于资源管理
- [强制]当升级或降级react-native版本时、必须进行代码备份; 说明: 升级失败或者涉及到原生代码时、可以进行代码回滚
- [强制]每个项目必须配置一个readMe文件、内容包括测试、正式环境等相关配置文件以及注意事项
#####日志管理
- [推荐]代码中过多使用console.log()会消耗性能、推荐去除不必要的日志输入代码
- [强制]在入口文件添加以下代码
if (!__DEV__) {
global.console = {
info: () => {},
log: () => {},
warn: () => {},
error: () => {},
};
}
说明: 可以在发布时屏蔽掉所有的console调用; React Native中有一个全局变量__DEV__用于指示当前运行环境是否是开发环境; 我们可以据此在正式环境中替换掉系统原先的console实现