不使用webpack或其他任何编译打包工具,但使用React的JSX混合语法,直接在浏览器中运行React的元件component,十分钟最快速上手。
image为什么用React?
-
数据绑定
假定我们需要在网页上呈现一个按钮和一个数字,每点击一次按钮,数字就会增加1,类似于购物车里面商品数量增加的情况。这个要怎么实现?
传统的办法是给按钮添加一个onClick事件函数,函数里面获取数字元素的html内容,增加1,然后再修改数字元素的html内容。
但现代化的网页开发就很不同,它会用数据把界面元素分隔开,即按钮修改数据,然后所有绑定到这个数据的文字都会自动同步,不需要去获取页面元素也不需要去修改页面元素。
界面A->修改->数据->自动同步->界面B。数据模型Model、界面View、以及事件控制器Controller,就形成了MVC的结构模式。
-
模块复用。
每个网站都有很多页面,大部分页面都有着同样的顶部导航栏。把导航栏代码复制到多个页面并不麻烦,但麻烦的是每次修改都要重新复制整理一遍。有没有办法能够重复使用一个导航栏元件,每个页面只要引入这个元件就可以?
解决各个模块的复用与整合问题,是现代化网页开发技术的初衷。在以往,我们可以依赖JQuery的ajax读取外部文件(比如顶部导航栏),然后把这个内容再填充到网页中合适的位置。这样做不仅代码麻烦,而且功能有限,更无法实现各个元件之间的结构嵌套和数据交互。
现代化网页开发技术依赖于component元件技术,让我们可以自己定制任何新的标签,比如顶部导航栏可以是一个
<TopNavBar>...</TopNavBar>
,它自身就是一个MVC结构,能够独立实现各种功能,也可以和父层、子层元素嵌套或者传递数据和功能。 -
单页面应用
既然我们可以利用component元素把页面各个模块划分成独立的部分,那么我们是否可以放弃页面的概念,而直接使用模块module来拼接各种功能呢?
当然是可以的,当我们迁移到模块式组装开发之后,由于各个模块实际上都运行在同一个页面内,互相交换和数据传递就变得自然而且直接,——不再受到页面切换只能依赖地址栏、Cookies或本地存储才能实现交互和数据传递。
单页面应用的优势是明显的,但缺点也很明显,那就是如果项目模块分隔的不好,或者项目文件组织管理混乱,那就很容易造成各个模块之间关系错综复杂,无法梳理。另外,缺乏了页面地址栏的变化,也需要重建新的路由模式,这也是个不小的挑战,比较原本基于页面地址栏跳转返回和历史记录的模式是如此成熟好用。
其实传统的页面也是一种模块划分的方法,它强制把不同的功能放入相互隔离的各个页面,以此来避免各种功能互相交织导致的项目复杂化。
SPA即Single-Page-Application,单页面应用。只是一种建议,并不是现代化网页开发的目标。
-
完全JS开发
现代化网页开发越来越倾向于完全依赖JS进行网页编写,即弱化或替代Html代码,VueJS和ReactJS都在这么做。
对于
<h1 id='a' class='b'>Hello!</h1>
这样的html元素,完全等同于js中的对象数据{tagname:'h1',content:'Hello!',id:'a',class:'b'}
,也就是说我们可以用js把后者完美的翻译成前者,然后插入到页面html中去。当然,如果我们能够在JS中直接使用html标签的话,那就太好了,可以同时享受JS模块化开发和熟悉的html传统语法。React很早就这么做了,那就是它的JSX语法。
但JSX语法显然不是浏览器支持的原生格式,所以需要使用Babel工具进行转换。
综上所述,所谓的现代化网页开发的变革,更像是朝着面向对象编程模式的进化,对象也好,模块也好,其实是一种应对复杂情况的功能组织管理模式,要应对更庞大更复杂的网站项目,就必须使用现代化的网页开发模式。
目前行业最主流的现代化网站开发框架就是VueJS、ReactJS和AngularJS。虽然VueJS最简单好用,但我还是选择了React,因为它更松散自由,可玩性更强,还有它那太好用的JSX语法!
基础模板
先看一下index.html
的代码:
<!DOCTYPE html>
<head>
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
</head>
<body>
<div id="app"></div>
<script src="lib/react/babel.min.js"></script>
<script type="text/babel" src="App.jsx"></script>
<script type="text/babel" src="./js/components/MyComponent.jsx"></script>
<script type="text/babel">
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
</script>
</body>
</html>
这里有一些说明:
- 首先要引入两个react的js文件。建议你把它们下载后放到本地,以免不测断网。
- 需要引入babel的js文件,用于支援jsx格式代码。
- 引入jsx格式代码的script标记内要增加
type="text/babel"
属性,页面内嵌的js也要加这个属性。 - 这里实际引入了两个components,一个是App,一个是MyComponent,元件的首字母应该大写。
- 页面中的
<div>
元素包含了id='app'
属性,这和下面script中的document.querySelector('#app')
对应。 -
ReactDOM.render(...
这个代码其实是固定语法,它的意思就是把这个带有id='app'
的<div>
替换成为App.jsx中的component元件。
入口元件
我们再来看App.jsx的代码:
'use strict';
let _App = (k) => (
<div>
<h2>{k.state.data}</h2>
</div>
)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 'pData',
};
}
render() {
return _App(this)
}
}
这个是最简化的代码,这里有一些说明:
-
'use strict';
是启用js语法严格模式,建议保留它。 - 带下划线的
_App
实际是一个html代码,这里是否带下划线或者用什么名字不要紧,但要想办法确保各个component中不能重名。 -
class App extends React.Component
表示这是一个React的元件的扩展,就是说继承出来的新元件,它一般都至少有两个函数constructor(props)
和render()
,前者负责管理数据模型Model,后者负责生成页面内容View。 -
super(props);
是继承父级类class的属性props,什么是props后面我们会谈论。 -
this.state
是关键的数据模型结构,原则上所有的数据都应该放到这里。 -
return _App(this)
,这是把component自身this传递到_App中去,即k,这样{k.state.data}
就可以自动实现绑定到constructor中的state数据上。
嵌套元件
我们把上面的入口元件扩展一下,让它直接使用我们在index.html中引入的MyComponent元件,改造后添加了一些增强功能,App.jsx代码如下:
'use strict';
let _App = (k) => (
<div>
<h2>{k.state.author}</h2>
<button onClick={k.clickHandler}>BUTTON</button>
<MyComponent pdata={k.state.tag} pfunc={k.clickHandler} app={k}></MyComponent>
</div>
)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 'pData',
tag: 'pTag'
};
this.clickHandler=()=>{
this.setState({'tag':'Hello!'})
alert('Parent btn click!')
}
}
render() {
return _App(this)
}
}
这里是一些说明:
-
<button onClick={k.clickHandler}...
我们添加了一个按钮,配了一个点击事件函数,它实际上是下面的this.clickHandler
函数,点击效果是直接修改数据Model中state的tag属性,注意要使用this.setState({'tag':'Hello!'})
这种命令才能自动同步到tag绑定的其他界面元素。 -
<MyComponent pdata={k.state.tag}..
,这个新标记就是我们在index.html中引入的MyComponent.jsx文件,这里我们把tag数据绑定到了pdata属性。对于MyComponent来说,这个pdata就是属性prop,后面还会讲到。 - 同样我们还向MyComponent传递了pfunc和app两个属性,App包裹了MyComponent,所以App是父层元件,MyComponent是子层元件,这相当于把父层的数据和函数传递到子层元件中。这里传递app的做法仅供试验,一般不推荐使用。
子元素
下面是MyComponent.jsx的代码,并不是最简的,仅供参考:
'use strict';
let _Kcodecell = (k) => (
<div>
<h4>{k.props.kata}</h4>
<button onClick={k.props.pfunc}>{k.props.pdata}</button>
<button onClick={k.props.app.clickHandler}>{k.props.pdata}</button>
<p>{k.state.name}</p>
</div>
)
class Kcodecell extends React.Component {
constructor(props) {
super(props);
this.state = { name: 'Kcodecell' };
}
componentDidMount() { }
componentWillUnmount() { }
render() {
return _Kcodecell(this)
}
}
这个代码和App.jsx结构完全相同,因为它们本来就都是component,完全没有区别。下面是一些提示:
- 两个
<button ...
并无不同,它们都绑定了父层传递进来的{k.props.pdata}
即_App.state.tag
,参见上面一段的第2个说明。prop即父层中给元件添加的属性<MyComponent pdata={k.state.tag}..
。 - 这两个button调用的onClick函数也相同,一个是从prop传递进来的pfunc属性
pfunc={k.clickHandler}
,另一个是app属性k.props.app.clickHandler
。它们都指向App中的this.clickHandler
函数。 -
componentDidMount
和componentWillUnmount
是React中Component的另外两个常用方法,简单说constructor
是在元件即将被放到页面中使用的运行,render
是放到页面的同时执行(就是这个函数把元件放到页面上的),componentDidMount
是在元件已经放到页面后马上执行,componentWillUnmount
是在元件被从页面上移除后立即执行。
回顾总结
上面代码实际上已经讲了React元件的管理和数据传递。
- App和MyComponent分别放到了单独的jsx文件中,index.html只剩下了最简单的一些代码。
- App可以嵌套MyComponent,可以通过props传递数据、方法甚至自身给子元件。
- 怎么把MyComponent的数据传递到父层App呢?其实我们完全可以在MyComponent中使用传递过来的k(即App)来直接操纵App的state数据模型,就相当于完全打通了父子层的数据。
最后,补充两点:
- 父子层之间的数据互相流动、互相操作是非常危险的。实际上很多主流的想法都是尽可能隔绝或限制元素之间的通信,称之为避免耦合。但我认为,一味的追求互相无关是没有意义的,我们需要的不是禁止或减少元件之间的联系,而应该是建立一种规范化的联系管道。——我们需要各个元件之间更好更多的互动,但又不希望这种关联让项目变得复杂失控。
- 这里讲的React开发和现在主流的开发方式有很大的区别,目前主流开发者都习惯使用各种自动化工具(如Webpack)管理项目各个元件代码的,这对于初学者来说还是比较麻烦,我也不认为那种模式是长久的趋势,处于简单易学的想法,这里完全回避了任何自动化工具的使用。
网友评论