我是怎么写一个React项目的

Robert Bao (鲍博) 首先感谢我的良师益友pinyin大神在我工作中不断给我带来的灵感。

关于组件

在React里,通常定义上我们认为组件就是React组件。但我要说的是React只是一个dom。

组件表示的一整套的东西,并且在配置好的环境底下,拿过来就可以用的。组件至少包含下面前两种东西:

1、datasource. 数据源

2、dom. 仅仅指virtual-dom

3、action. 描述你期望对数据源做什么的对象可以是这样的: {type: “UPDATE_VIEW”, payload: “xxxx”}

4、其他. 包括样式,图片等等

所以一个正常的组件目录看起来是这样的:

关于视图

视图是一种高阶组件,主要工作之一是获取UI数据,关于获取数据请看后面

视图的另一个主要工作就是是组织布局的,也可以没有布局,直接组织dumb组件

视图不允许出现html(jsx),当然根节点那个div无法避免

视图只需要dom和data,操作视图的行为是属于私有的,只允许通过props传给子组件,这是符合依赖倒置原则的(ioc)

视图和组件之间应该保持视觉上的父子关系,这样很OOP!

import * as React from 'react'
import {lift, resource} from 'meng'
import Banner from '../common/banner/banner'
import DetailHeader from './detail-header/detail-header'
import DetailLike from './detail-like/detail-like'
import Container from '../common/container/container'
import Bottom from '../common/bottom/bottom'
import Path from '../common/path/path'
import {getById, getByCollocation} from './detail.api'
@resource(props => getById(props.params.cid), "data")
@resource(props => (window.scrollTo(0, 0), getByCollocation()), "cos")
@lift({ data: {}, cos: [] })
export default class Detail extends React.Component<any, any> {
render() {
return (
<div>
<Banner />
<Path />
<Container>
<DetailHeader data={this.props.data} />
<DetailLike data={this.props.cos}/>
</Container>
<Bottom />
</div>
)
}
}

关于dumb组件

所谓dumb组件就是视图直接用到的组件了。

dumb组件不允许自己获取UI数据

dumb组件唯一合法的数据源就是props,不要写其他副作用代码,比如document.body.clientWidth。也就是说要么是来自你的数据仓库要么就来自上层组件。

dumb组件只允许处理交互数据

dumb组件有自己的样式

关于数据源

讲到怎么给React注入数据源,我先介绍几种常见的。

第一种是React原生写法,直接在componentDidMount方法里面调用接口,然后setState,他看起来是这样的:

componentDidMount(){
fetch(url).then(data => this.setState({data: data}))
}

第二种写法我想介绍redux — 在componentDidMount方法里面dispatch(action),看起来是这样的

componentDidMount(){
this.props.dispatch(action).then(data => this.setState({data: data}))
}

其他一些写法我都不举例了,总之就是太蠢了,好像在教React怎么去获取数据一样。

下面介绍我的独一无二的声明式数据源的写法

import Store, { lift, resource } from 'meng'
@resource(fetch(url), "data")
@lift()
export default class App extends React.Component<any, any>{
render() {
const data = this.props.data
}
}

那么这种声明式的优势在哪呢?

很明显帮你节省了很多代码,减少了没必要的复杂性和增加了逻辑上的可读性。这就像React声明了1个叫data的数据源,待fetch成功取出数据之后就会注入到this.props里面。不管你怎么想,声明式数据源也是未来趋势之一。

React还有一个容易被忽视的切有点恶心的点,请看下面代码

import Store, { lift, resource } from 'meng'
export default class App extends React.Component<any, any>{
componentWillReceiveProps(nextProps) {
if(!shallowEqual(this.props, nextProps)){
fetch(url).then(something)
}
}
}

当路由改变而你的页面无需改变也就意味着你的组件也不需要改变,你写在didmount里面获取数据的方法又需要在上面的componentWillReceiveProps再写一遍,这就写了两遍,代码也变得不优雅起来,为了让声明式进行到底,我是这么写的:

import Store, { lift, resource } from 'meng'
@resource(props => fetch(`url/${props.xxx}`), "data")
@lift()
export default class App extends React.Component<any, any>{
render() {
const data = this.props.data
}
}

我真不是来安利meng.js的…我只是想说我的meng在设计上已经非常先进,满足各种fp党和声明党。以后一定会有比我写的更好的国外同类产品。

关于css

为什么我要把css单独拿出来提,因为我的css真的很不一样。

前端想来就是一件事,那些把前端搞成js,html,css的真的该醒醒了,比如ng系列始终放不下的模板。那么css用css module也就水到渠成了,这样css和js就是一件事了。关于css module也有区别的:

1、import “xxx.css”.

这估计是大多数小伙伴们使用的方法,这种方法简单易上手,还是在写熟悉的css。但是这种写法有个最大的问题就是命名冲突问题,css都是全局的,免不了一些命名上的麻烦。

2、import {classname} from ‘xxx.css’.

这种写法是目前看起来最好的方法。导出的类名是一串随机字符串,直接在组件里面使用这个变量就好了,避免了命名上的问题

3、css in js

这是我现在用的方法。他看起来是这样的

import FreeStyle from 'free-style'
const Style = FreeStyle.create()
const logo = require("./logo.png")
const LOGO = Style.registerStyle({
background: `url(${LOGO})`
})
Style.inject()

我借助了free-style插件帮我实现了css in module,它有以下好处

1、这是纯js的,没必要在css和js之前切换

2、因为是纯js的,他也继承了js的可计算性

3、如果你喜欢你可以在任何时候inject样式,相比上面第二种css module有更多的可控性

4、如果你使用typescript的话,他还支持静态分析,更模块化

当然他也有一点点弊端:json写起来比css累而且没代码提示。不过如果你是css老手的话这应该难不倒你。

关于媒体查询

之前有过这么一个需求,根据不同的手机宽度,展示不同的logo。

有经验的css高手都会告诉你用媒体查询啊这还不简单,然而事情不是这么简单,这里又的亏pinyin大神点醒我,参阅了一些文章,我总结下我为什么不用媒体查询。

1、媒体查询需要查询的是元素本身而不是屏幕或者viewport,考虑下面的代码:

@media(max-width < 600) {
.test {
font-size: 12px
}
}

当屏幕宽度小于600px的时候,test类字体设置成12px,然而这种虽然能达到我们的目的,但是我们更期望结果是这样的:

.test @media(max-width<600) {
font-size: 12px
}

我们期望.test能根据自身宽度进行查询,从而设置自己的字体,很显然媒体查询没办法做到

2、媒体查询仅仅只能改变布局,然而真正的响应式不只是布局的改变,可能包含计算,再往大了点,可能直接修改dom,这些媒体查询都没办法做到

3、媒体查询写在全局是一种抽象泄漏。媒体查询和css一样,你的任何代码如果符合他的规定,你的样式就莫名其妙的变动了,所以你不得不小心翼翼的处理这些全局变量,媒体查询在现在看起来显得有点黑科技了,是时候抛弃他们了,至少react可以做的更好

那么我们应该怎么写媒体查询呢?

根据上面的观点,我们可以期望下想象中的媒体查询组件是什么样的:

组件必须是响应式的,我们的组件能根据现有数据对自身进行查询,能改变布局,能计算,能改变dom,而且clsss和媒体查询都必须是私有的

综合有js计算能力的css in js和React+meng.js刚好就能做到,贴点代码:

import * as Style from './banner.style'
import Store, {lift, resource} from 'meng'
const logo320 = require("./logo_320.png")
const logo375 = require("./logo_375.png")
const logo414 = require("./logo_414.png")
@lift({})
export default class Banner extends React.Component<{ width: number }, void> {
render() {
const logo = checkLogo(this.props.width)
return (
<div className={Style.LOGO} style={{ background: `url(${logo}) no-repeat center center` }}></div>
)
}
}
const checkLogo = vw => {
if (vw <= 320) return logo320
else if (vw > 320 && vw <= 375) return logo375
else return logo414
}

狗日的编辑器不好用啊。

上面的代码虽然没展示怎么改变dom,但是熟悉react的同学都知道这不是事儿~

好啦就这样吧

Show your support

Clapping shows how much you appreciated coral’s story.