React因为jsx的模式,比Vue的写法更多,更杂乱,但胜在社区广,开发者多,作为Facebook为后盾的开发者团队们,更是底蕴深厚,出了一套又一套的扩展插件,理念也各不相同,有react-router3/4扁平化结构与过程式开发的碰撞,有css in js与传统less、sass等的交错,确实,他们让react的羽翼更加丰满,选择上更加自由,不过如此带来的代价就是规范上无法统一,在此编写react书写规范,属个人规范性文章,仅供参考。
create-react-app my-app –scripts-version=react-scripts-ts
yarn eject
my-app
|
|--build
|--config
|--node_modules
|--public
|--scripts
|--src
|--api
| |--config.ts
|
|--base
| |--better-scroll
| | |--index.tsx
| | |--css.less
| |
| |--slide-page
| | |--index.tsx
| | |--css.less
| |
| |--top-header
| | |--index.tsx
| | |--css.less
| |--......
|--common
| |--fonts
| | |--......
| |--js
| | |--adaption.ts
| | |--fetch-ajax.ts
| | |--methods.ts
| | |--......
| |--style
| | |--base.less
| | |--index.less
| | |--public.less
| | |--......
|--components
| |--login
| | |--index.tsx
| | |--css.less
| |--my
| | |--index.tsx
| | |--css.less
| | |--det
| | | |--index.tsx
| | | |--css.less
| | |--......
| |--index.tsx
|--store
| |--modules
| | |--order.ts
| | |--user.ts
| | |--......
| |--index.ts
|--index.tsx
|--registerServiceWorker.ts
|--.gitignore
|--images.d.ts
|--package.json
|--README.md
|--tsconfig.json
|--tsconfig.prod.json
|--tsconfig.test.json
|--tslint.json
|--yarn.lock
AJAX统一封装,公用组件跟业务组件分离,公共文件统一common接入,公用对象store,架构简洁明了,如用的是react-router3,则建立单独router目录,进行扁平化管理。
/* 调用模块 */
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { HashRouter, Route } from 'react-router-dom'
import registerServiceWorker from './registerServiceWorker'
......
/* 全局挂载 */
import 'common/js/adaption.ts' // rem 自适应
import 'store/index.ts' // redux window._STORE
import 'api/config.ts' // ajax window.API
......
/* 全局样式 */
import 'antd/lib/notification/style/css'
import 'antd/lib/input/style/css'
import 'common/style/index.less' // 自定义全局样式要在最后引入
/* 业务组件唯一入口 */
import App from './components/index'
declare global { // 定义暴露全局的属性
interface Window {
API: any,
_STORE: any
}
}
/* 渲染 */
ReactDOM.render(
<HashRouter>
<Route path="/" component={ App } />
</HashRouter>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker()
没啥好说的,最外层的index.tsx必须保持纯洁性
/* 调用模块 */
import * as React from "react"
import { Route } from "react-router-dom"
import { $getTimeStore } from "common/js/methods.ts"
/* 公用组件 */
import Tab from "base/tab/index"
import SlidePageRouter from "base/slide-page/index"
......
/* 业务组件 */
import My from "components/my/index"
import TakeOut from "components/takeOut/index"
import Login from 'components/login/index'
import TakeOutSeach from 'components/takeOut/seach/index'
import TakeOutDet from 'components/takeOut/det/index'
......
/* 入口对象类型定义 */
interface Props extends React.Props<any> {
history: any
......
}
/* 唯一的模块导出 */
export default class App extends React.Component<Props, any> {
constructor(props: any) {
super(props)
this.state = { // 定义该App组件所有对象集
'test': 123,
'_tab': { // *定义公用模块Tab
'main': { // *定义公用模块Tab的版本 --- 版本为‘main’
item: [ // *定义详细参数
{
id: "food",
name: "外卖",
components: TakeOut
},
{
id: "my",
name: "我的",
components: My
}
],
itemClick: (item: any, arr: any) => {
// ......
},
itemSelect: "food"
}
},
'_slidePage': { // *定义公用模块slidePage
'normal': {} // *定义公用模块slidePage的版本 --- 版本为‘normal’
}
}
this['methods'] = { // *定义该App组件所有方法集
test: () => {
// ......
}
}
}
public render() {
return (
<div className="App">
this.state._tab} />
this.state._slidePage}>
"/login" component={ Login } />
"/takeOutSeach" component={ TakeOutSeach } />
"/takeOutDet/:val" component={ TakeOutDet } />
......
......
div>
)
}
}
什么模块就干什么事,组件公用对象就老老实实的在state里定义,拒绝东一处西一处
公用组件传参以版本式传参,以type为唯一参数入口,拥有更高的可读性与维护性,下一例子说明
组件方法统一封装至methods(其实定义在state里也不是不可)
方法不挂在原形下,保持react生命周期的整洁性(这里确实多多少少有被Vue影响)
/* top-header 公用组件 */
import * as React from 'react';
import { Input } from 'antd'
import { Link } from "react-router-dom"
......
/* 入口对象类型定义 */
interface Props extends React.Props {
type: any
......
}
/* 唯一的模块导出 */
export default class Top extends React.Component<Props, any> {
constructor (props: any) {
super(props)
require ('./css.less')
const key = Object.keys(this.props.type)[0]
this.state = {
'key': key,
'data': this.props.type[key]
}
}
/**
* 版本:normal
* @param { String } left 'fa-angle-left'
* @param { String } right 'fa-angle-left'
* @param { String } title '首页'
*/
public normal (state: any = { // 默认参数
left: {
icon: 'fa-angle-left',
to: '/'
},
right: {
icon: '',
to: '/'
},
title: ''
}) {
return (
"Top-normal">
{ state.left && }
{ state.title }
{ state.right && }
)
}
/**
* 版本:seach1
* @param { String } left 'fa-angle-left'
* @param { Function } call 'val => {}'
*/
public seach1 (state: any = { // 默认参数
left: {
to: '/',
icon: 'fa-angle-left'
},
call: (val: any) => (console.log(val))
}) {
return (
"Top-seach1">
"input search text"
onSearch={ state.call }
className="input"
/>
)
}
/**
* 版本:seach2
* @param { String } to '/takeOutSeach'
*/
public seach2 (state: any = { // 默认参数
left: {
to: '/',
icon: 'fa-angle-left'
}
}) {
return (
"Top-seach2">
"link" to={ state.to }>
true }
placeholder="click this seach"
className="input"
/>
)
}
......
public render () {
return this[this.state['key']] && this[this.state['key']](this.state['data'])
}
}
所有公用模块的constructor与render都是一样的,可变的只有中间的版本,好处自行体会
const PATH = (path) => (require('components/' + path +'.jsx').default)
export default [{
path: '/',
component: PATH('index'),
childRoutes: [
{
path: 'login',
component: PATH('login/index')
}, {
path: 'takeOutSeach',
component: PATH('takeOut/seach/index')
}, {
path: 'takeOutDet/:val',
component: PATH('takeOut/det/index'),
childRoutes: [
......
]
},
......
]
}]
能写一遍别浪费时间写第二遍
import { notification } from 'antd'
import { GET, POST } from 'common/js/fetch-ajax.ts' // 可对库进行抉择
// const FAKE = false // true:假数据 false:真数据
// const URL: string = 'http://192.168.0.103' // 测试服务器
// const URL: string = location.protocol + '//' + location.host + '/api' // 用于反代
const URL: string = 'http://XXX.XXX.XXX.XXX' // 正式服务器
const CODE_OK: number = 0
const CODE_ERR = (r: any, type?: boolean) => { // 失败的回调
notification[type ? 'warning' : 'error']({
message: 'Notification Title',
description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
})
console.error(r)
}
const CODE_IS = (r: any, fn: any) => (r.status === CODE_OK ? fn(r) : CODE_ERR(r, true))
// 可进行数据预处理,避免直接操作业务组件 --- 如果模块复杂,多人协作开发的话,以下API模块可写成中间件导入
window['API'] = {
'takeOut-getList' (fn: any) {
GET(URL + '/tpadmin/public/index.php/api/user/cplist').then((res: any) => res.json()).then((res: any) => CODE_IS(res, fn)).catch((err: any) => CODE_ERR(err))
},
'login' (data: object, fn: any) {
POST(URL + '/tpadmin/public/index.php/api/user/log', data).then((res: any) => res.json()).then((res: any) => {
// 这儿可以进行过程控制,避免动业务组件
...... ? CODE_ERR(res, true) : fn(res)
}).catch((err: any) => CODE_ERR(err))
},
......
}
上面真/假数据,例子没写,其实是有必要存在的,有些时候就是会出现一些服务器挂掉或某个接口挂掉,后端来不及的情况下,产品要你假数据先塞上去
该细的地方细,该实用的时候就得简单粗暴,API直接挂在window下,并不会损耗多少内存,反过来,你每个模块都得引入一下,开发效率只会只低不增。
// localStorag - 存储信息有效期
export function $setlocalStorag(
name: string,
value: any,
timeout: number = 365 * 24 * 60 * 60 * 1000
) {
let now: number = Date.now()
timeout = now + timeout
value = Object.assign(value, {
'savedate': now,
'timeout': timeout
})
localStorage.setItem(name, JSON.stringify(value))
}
// localStorag - 获取信息有效期
export function $getTimeStore(name: string) {
let getLocal: any = localStorage.getItem(name)
let data: any = getLocal ? JSON.parse(getLocal) : {}
let now: any = Date.now()
if (data.timeout) {
return data.timeout < now ? {} : data
} else {
return {}
}
}
// localStorag - 删除信息
export function $deleteStore(name: string) {
localStorage.removeItem(name)
}
export function $id(id: string) {
return document.getElementById(id)
}
export function $class(klass: string) {
return document.getElementsByClassName(klass)
}
......
公用方法统一每个都export导出,import依赖注入,不要用定义对象的方式,最后用export { XX, XX, …… }导出,用过的都懂,反正都是进来ctrl+f搜索的
每个导出对象,都加个标识符,例如:‘$’,用于区分组件内的私有函数
/* index.less */
@import "./base.less";
@import "./public.less";
#root .App {
overflow: hidden;
}
为什么只有两个?你看目录架构就知道了,每个人对模块化的理念是不一样的,而我觉得将css、image进行模块化目录,与业务组件一般无二的时候,我认为是冗余的
base为初始化css,public为全局css,起初我认为全局变量也是必要的,将它配置至webpack中,但后来发现,全局变量根本没全局属性好使。
这边有些话要先说,因为跟个人理念有关
redux的优势很多,不过根据架构来看,有很多也是没必要的。
那这边针对第二点,进行书写
/* user.ts */
const type: string = 'user'
const data: object = {
name: '',
......
}
export default function (
state: object = data,
action: any
) {
return action.type !== type ? state : Object.assign({}, state, action.param)
}
/* index.ts */
import { combineReducers } from 'redux'
import { createStore } from 'redux'
import { $setlocalStorag } from 'common/js/methods.ts'
const PATH = (path: string) => (require(path + '.ts').default)
window._STORE = createStore(combineReducers({ // 中转合并
user: PATH('./modules/user'),
order: PATH('./modules/order'),
......
}))
window._STORE.subscribe(() => { // 数据变动则自动存储localStorag
let state = window._STORE.getState()
Object.keys(state).map(key => $setlocalStorag(key, state[key]))
})
/* put */
window._STORE.dispatch({
type: 'user',
param: {
name: 'Hello World!',
......
}
})
/* get*/
import { $getTimeStore } from "common/js/methods.ts"
console.log($getTimeStore("user").name) // Hello World!
因为是localStorage+redux的配合,所以localStorage自带一些API,别直接改
所有数据缓存,我认为它很重要,无疑是既优化了UE,又优化了后端dataTimeOut的问题,但劣势也很明显,更加复杂化了工程,store层将会跟业务组件一样拥有相同的目录结构,它又无法与less一般嵌入过程式开发的业务组件中(因为它还可能是公用组件数据),如果store分公用跟私用?那是否要抽离业务组件的state直接映射私有store,但事实上也无法完全抽离,有时还是会存在不存store的state,私有store就会有私有commit,方法层也得抽离成模块,开发视图会变的非常复杂……零零碎碎的模块化开发与个人的组件式开发违背,不喜, 但优势也确实存在,所以各有优劣,需取舍
make:o︻そ╆OVE▅▅▅▆▇◤(清一色天空)
blog:http://blog.csdn.net/mcky_love
掘金:https://juejin.im/user/59fbe6c66fb9a045186a159a/posts