中兴 a2015高配版天机7高配版多少钱

动手实现数据双向绑定React Dom(4) - 简书
动手实现数据双向绑定React Dom(4)
React.js 已经成了当红炸子鸡,不会React.js都不好意思说自己会前端了。其中,虚拟DOM是其主要的特性。
那么它相对于其他双向绑定模有什么异同吗?
无论是angular还是vue,对于动态绑定的重新渲染,取决与model的数值变化,而react则是虚拟树的比较。我们来看看react的渲染流程。
1.创建一棵虚拟的dom树。2.渲染到真实dom中。3.model数据变化,告诉react重新在内存中生成新的虚拟dom树。4.新的虚拟dom树与旧的dom树进行对比,提取两棵树不同的节点部分。5.将变化部分,渲染到真实的dom上。
来谈谈react.js的优势
1.react关注的是渲染效果,如果渲染效果一样的情况,哪怕model产生变化也不会产生重新渲染,举个例子,&p&{{demo1}}&/p& ,假设demo1变量数值是hello world,那么渲染出来的dom 即是 &p&hello world&/p&,当我们换了一个变量demo2,即&p&{{demo2}}&/p&demo2`的数值也是"hello world",对于传统的双向绑定框架,肯定认为数值变化了,需要重新渲染,而react因为是直接对新旧树之间的比较,两个树一样旧不用重新渲染了。
2.简化了框架的更新逻辑,在前文中,可以看出来我直接让model属性与dom元素绑定了,当我修改model属性的时候,能够直接修改dom元素,能做到O(1)的搜索,这是我对mvvm的简单优化,但对于一个复杂mvvm框架来说,算法可没有那么容易,dom结构随时都可能被删除,添加各种操作,稍微不慎就会导致更新失败或无法做到优化,对于虚拟dom稳定控制在o(n)内,无论操作有多么复杂。
3.以上都是我个人理解reactjs的优势,知乎里面也有精彩的讨论,可以参考
1.构造虚拟dom树系统,是比较复杂的代码,足够写一篇文章,由于不是本文的重点,这里采用的是开源项目
主要使用的是
var svd = require('simple-virtual-dom')
var el = svd.el //构造dom树元素
var diff = svd.diff // 对新树与就树之间做对比,找出不同的地方
var patch = svd.patch // 将dom树变动部分变化,应用到原来的dmo上
2.生成虚拟dom树
genVTree: function (model) {
return el('div', {'id': 'container'}, [
el('span', [model.time])
3.插入虚拟dom树到真实dom
var vTree = this.genVTree(this.model);
this.tree = vT
this.root.appendChild(vTree.render());
4.产生数据变化,当数据变化的时候,重新生成虚拟dom树,与旧的dom树进行比较,找出不同的dom部分,并将该部分的改动应用到真实dom树里。
setState(object) {
var self =
var oldTree = this.
Object.keys(object).forEach(function (key) {
self.model[key] = object[key];
var newTree = this.genVTree(this.model);
this.tree = newT
var patches = diff(newTree, oldTree);
patch(this.root, patches);
setInterval(function () {
demo.setState({time: Date()})
5.代码实现
如果有一天我死了,请把我埋在二次元.一、首先是Showcase
See the Pen &a href="http://codepen.io/charleyw/pen/KVgNeK/"&http://codepen.io/charleyw/pen/KVgNeK/&/a&‘&react-tabs by Wang Chao (&a href="http://codepen.io/charleyw"&http://codepen.io/charleyw&/a&‘&@charleyw) on &a href="http://codepen.io"&http://codepen.io&/a&‘&CodePen.
二、如何实现
既然用React写,那么它就必然是一个组件,首先考虑你怎么使用这个组件,也就是这个组件的接口是怎么样的。
name="red"&
className="red"/&
name="blue"&
className="blue"/&
name="yellow"&
className="yellow"/&
这个TabsControl作为父组件,它来控制Tab的如何切换,Tab是用来包裹真正要显示的内容的,它的name属性是这个标签页的名字,会被显示在标签页的标题栏上。
三、创建基本元素
按照之前的想法,我们用Tab定义了很多个标签页,我们需要根据这些定义生成标签页的标题栏和内容。
1. 遍历this.props.children动态生成标题栏
this.props.children是React内建的一个属性,用来获取组件的子元素。因为子元素有可能是Object或者Array,所以React提供了一些处理children的辅助方法用来遍历:React.Children.map
那么动态生成标题的代码可能是这样子的:
React.Children.map(this.props.children, (element, index) =& {
return (&div className="tab-title-item"&{element.props.name}&/div&)
2. 再用同样方法生成标签页内容
React.Children.map(this.props.children, element =& {
return (element)
组合起来就是TabsControl的实现:
let TabsControl = React.createClass({
render: function () {
let that = this;
let {baseWidth} = this.
let childrenLength = this.props.children.
&nav className="tab-title-items"&
{React.Children.map(this.props.children, (element, index) =& {
return (&div className="tab-title-item"&{element.props.name}&/div&)
&div className="tab-content-items"&
{React.Children.map(this.props.children, element =& {
return (element)
加上一些css就能看到一个标签页的雏形了。
三、实现标签页切换
这里要出现React最重要的概念了state,state是一个Javascript的Object,它是用来表示组件的当前状态的,如果用TabsControl举例的话,它的state可以是当前处于激活状态的标签页编号(当然,如果你想的话也可以保存标签页的内容)。
React提供了一个方法setState()让你可以改变state的值。每次调用setState()都会触发组件的render(),也就是说会把组件所代表的DOM更新到state所代表的状态。
所以实现切换的关键如下:
1. state保存当前处于激活状态的标签页的编号
1. 点击标题的时候调用setState()更新激活的标签页编号
2. render()的时候,在遍历this.props.children的时候把编号与state中编号一致的元素标记为active
3. 用css将非active的元素隐藏起来
所以代码是这样的:
let TabsControl = React.createClass({
getInitialState: function(){
return {currentIndex: 0}
getTitleItemCssClasses: function(index){
return index === this.state.currentIndex ? "tab-title-item active" : "tab-title-item";
getContentItemCssClasses: function(index){
return index === this.state.currentIndex ? "tab-content-item active" : "tab-content-item";
render: function(){
let that = this;
let {baseWidth} = this.
let childrenLength = this.props.children.
&nav className="tab-title-items"&
{React.Children.map(this.props.children, (element, index) =& {
return (&div onClick={() =& {this.setState({currentIndex: index})}} className={that.getTitleItemCssClasses(index)}&{element.props.name}&/div&)
&div className="tab-content-items"&
{React.Children.map(this.props.children, (element, index) =& {
return (&div className={that.getContentItemCssClasses(index)}&{element}&/div&)
getInitialState:是组件的初始化状态,默认是第一个标签页处于激活状态。
getTitleItemCssClasses:判断当前标签和state中保存的标签编号是否一直,是则标识为active。
getContentItemCssClasses:同上。
onClick={() =& {this.setState({currentIndex: index})}}:标签页标题绑定了点击事件,每次点击都会更新state保存的标签页编号,然后触发render()方法重绘标签页。
上面一系列的操作最终的结果都需要用render()来反应出来,所以关键点是如何在render()中使用state来动态生成DOM.
接下来的改进
实现可以滑动的标签页
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:7360次
排名:千里之外
(1)(2)(1)(2)(1)React入门(2)——状态、事件与动态渲染-爱编程
React入门(2)——状态、事件与动态渲染
本文主要是记录作者在官网学习如何使用JSX+ES6开发React的过程。
全文共分为3篇内容:
列表、键值与表单
组件状态和生命周期
& & 上一小节说明了组件传入的参数必须是只读的,但是在丰富的前端应用中,页面样式是时时刻刻都有可能发生变化的。在前面的章节中介绍了一个时钟的例子,通过重复调用ReactDOM.render()&来渲染组件:
function tick() {
const element = (
&h1&Hello, world!&/h1&
&h2&It is {new Date().toLocaleTimeString()}.&/h2&
ReactDOM.render(
document.getElementById('root')
setInterval(tick, 1000);
& & 现在介绍另外一种方法实现一个时钟组件,让其真正的具有封装和可复用特性。我们先设计一个时钟类的基本结构:
function Clock(props) {
&h1&Hello, world!&/h1&
&h2&It is {props.date.toLocaleTimeString()}.&/h2&
function tick() {
ReactDOM.render(
&Clock date={new Date()} /&,
document.getElementById('root')
setInterval(tick, 1000);
&&&&然而,现在的Clock只是实现了显示当前时间而已,他需要使用tick反复的调用 ReactDOM.render()&实现Dom渲染。
& & 按照封装的概念,我们需要像下面这样来使用一个时钟组件:
ReactDOM.render(
&Clock /&,
document.getElementById('root')
& & React组件提供了一个状态量(state)来实现自我状态的控制。
& & state和props类似,但是他是组件内部私有的变量,而且完全由组件自己控制。
& & 在本文前面已经提到,如果使用es6风格的“类”(class)创建组件,可以提供很多额外的特性,state也只能通过class来实现。
-----------------------------------------------
& & 附注:
& & 可能很多一直从事前端开发的童鞋并不太关注面向对象的概念,而使用Java/C++这一类结构化语言的童鞋应该很容易理解后文中将要提到的 类、构造函数、成员变量、成员方法、继承等概念。为了便于阅读这里列举出了中文概念和代码中属于的对照表,帮助大家有效的对照代码理解:
& & 类: class
& & 对象:object
& & 实例:instance
& & 功能函数:function
& & 成员方法:method
& & 构造方法(构造函数):constructor
--------------------------------------------------
用class替换function来创建一个组件
& & 使用class替换function创建一个组件只需要以下五个步骤。
创建一个和function一样名称的class并且继承ponet。
在class中增加一个名为render()的方法。
将function中的代码移动到render()方法中。
在render中用this.props替换props参数。
删除已经没用的function。
& & 下面的代码是用class创建一个组件:
& & &ES6语法:
class Clock ponent {
render() {
&h1&Hello, world!&/h1&
&h2&It is {this.props.date.toLocaleTimeString()}.&/h2&
& & JavaScript基本语法:
var Clock = React.createClass({
render: function () {
&h1&Hello, world!&/h1&
&h2&It is {this.props.date.toLocaleTimeString()}.&/h2&
& & 使用class可以让我们为组件添加更多的属性。
向class中增加本地的state
& & 下面将暂时如何使用组件的state特性。
& & 1.将render()中 this.props.date 替换成&this.state.date:
class Clock ponent {
render() {
&h1&Hello, world!&/h1&
//使用state来定义变量
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
// ——————
& & 2.创建一个constructor,并在constructor中初始化this.state:
&&&&ES6语法:
class Clock ponent {
//构造方法
constructor(props) {
//构造父类
super(props);
//初始化this.state
this.state = {date: new Date()};
render() {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
& & JavaScript语法:
var Clock = React.createClass({
//官网的最新文档已经说明在ES6中使用构造方法来替换getInitialState初始化state
getInitialState: function () {
return {date: new Date()};
render: function () {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
&&&&在构造方法中可以获取到当前组件的属性值props,因此我们可以在此将props赋值给state。
& & 3.移除ReactDOM.render渲染组件时使用的属性:
ReactDOM.render(
&Clock /&,//移除date={new Date()}的属性
document.getElementById('root')
向类中增加事件方法(Lifecycle Methods)
&&&&在一个包含了很多组件的系统中,组件被创建或销毁时进行资源管理是一项非常重要的工作。在react中提供了“mounting”(安装)方法,它会在组件被渲染到Dom之前会被调用。而“unmounting”(卸载)方法会组件被从Dom删除之前调用。
& & 我们可以在class中定义多种method来获取各种事件,如下例:
& & ES6语法:
class Clock ponent {
constructor(props) {
super(props);
this.state = {date: new Date()};
componentDidMount() {//组件完成Dom渲染时会被调用
componentWillUnmount() {//组件从Dom删除之前会被调用
render() {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
& & JavaScript基本语法:
var Clock = React.createClass({
getInitialState: function () {
return {date: new Date()};
componentDidMount:function() {
componentWillUnmount:function() {
render: function () {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
& & 在代码中componentDidMount()方法是在组件被渲染到Dom中后会被调用,这里最适合创建一个时间计数功能:
componentDidMount() {
this.timerID = setInterval(
() =& this.tick(),
& & 创建了计数功能的同时,我们将一个timerID作为一个变量存储到this中,this表示当前组件的一个实例(instance),我们可以将任何和组件实例先关的变量都存储到this中,以便在所有方法中使用(学Java/C++的童鞋,我不多说,这就是成员变量和成员函数)。因此,当组件将要被销毁时,我们应该移除这个时间计数器:
componentWillUnmount() {
clearInterval(this.timerID);
&& & 最后,创建一个tick()方法用来持续更新时间。在tick()方法中会使用state来更新组件,下面是这个组件的完成代码:
& & ES6语法:
class Clock ponent {
constructor(props) {
super(props);
this.state = {date: new Date()};
componentDidMount() {//组件渲染之后创建计数器
this.timerID = setInterval(
() =& this.tick(),
componentWillUnmount() {//组件销毁之前移除计数器
clearInterval(this.timerID);
tick() {//使用父类ponent的setState()方法更新state:设置当前时间
this.setState({
date: new Date()
render() {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
ReactDOM.render(
&Clock /&,
document.getElementById('root')
JavaScript基本语法:
var Clock = React.createClass({
getInitialState: function () {
return {date: new Date()};
componentDidMount: function () {
this.timerID = setInterval(function () {
this.tick();
componentWillUnmount: function () {
clearInterval(this.timerID);
tick:function () {
this.setState({
date: new Date()
render: function () {
&h1&Hello, world!&/h1&
&h2&It is {this.state.date.toLocaleTimeString()}.&/h2&
ReactDOM.render(
&Clock /&,
document.getElementById('root')
& & 让我们在重现一下组件到底做了什么,了解类中每一个method调用的顺序:
当调用&ReactDOM.render()&时,我们传递了&Clock/&参数。React会调用Clock组件的构造函数(constructor)。由于组件需要显示当前的时间,在构造函数中使用一个时间对象的实例赋值给了state:this.state = {date: new Date()}; 。随后会更新state。
随后React会调用Clock组件的 render()方法。在 render() 方法中会返回一个类似于html标签的结构,这个结构告诉react应该在浏览器中显示什么样的内容。render()返回之后,React会向浏览器渲染这个dom结构。
在React使用render方法的返回值渲染Dom之后,&componentDidMount() 会被调用,在这个方法中,组件使用浏览器提供的基础方法创建了一个timer实例,并定期调用 tick() 方法。
浏览器每秒都会调用 tick()&方法,这个方法中组件调用父类的setState方法来定期更新页面上展示的时间数据。由于继承自父类ponent,每次调用 setState() 方法都会更新this.state的值,并且告知React状态发生了改变,React会再次使用 render() 方法使用最新的state值更新对应的Dom。
当Clock组件被从Dom移除时,React会调用组件的&componentWillUnmount() 方法移除timer。
正确的使用state
& & 在使用 setState() 方法时有三点需要了解:
& & 切勿直接修改state
& & 例如使用下面的方法组件将不会重新渲染:
ment = 'Hello';
& & 必须使用 setState() 来更新组件:
// Correct
this.setState({comment: 'Hello'});
& & 仅仅只能在构造函数中给this.state赋值。
&&&&state异步更新
&&&&React在某些情况下会一次性更新多次setState调用,而不是每次调用setState都会直接更新。因此this.props或this.state可能会出现异步更新的情况,因此某些累计或累加型的运算切勿直接使用setState。例如下面的这个例子:
this.setState({
counter: this.state.counter + this.props.increment,
& & 想要使用counter记录累加的结果,但是在某些时候counter并没有被更新。为了解决这个问题,React提供了一个setState的重载方法:setState(function(prevState,props)),例如:
// Correct
// es6函数式
this.setState((prevState, props) =& ({
counter: prevState.counter + props.increment
// JavaScript
this.setState(function(prevState, props){
counter: prevState.counter + props.increment
& & 使用 setState的 重载方法后,在function中接受的第一个参数是前一个状态值,而第二个参数是当前props值。
& & state的更新会被合并
& & 当调用setState时,React会将上一次更新的值和本次更新的值进行合并。例如在state中包含相互独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
然后我们可以使用setState单独更新他们:
componentDidMount() {
fetchPosts().then(response =& {
this.setState({
posts: response.posts
fetchComments().then(response =& {
this.setState({
comments: ments
数据单向性
& & 无论父组件还是子组件,都无法知晓其他组件的状态,只能在内部封装中调用&setState()&方法,除了拥有他会设置他的组件之外,其他组件均无法调用到它。
& & 父组件可以将state值作为一个属性(props)传递给子组件,如下:
&FormattedDate date={this.state.date} /&
function FormattedDate(props) {
return &h2&It is {props.date.toLocaleTimeString()}.&/h2&;
&&&&FormattedDate组件可以通过自己的props获取date值,但是它并不知道这个值是Clock的state值。 & & 数据单向性保证所有的状态值(state)只能在组件内部使用(封装特性),而所有组件只能影响它内部派生的组件。
& & 组件是相互独立的,即使是同一个组件,在不同的地方使用会产生不同的实例。看下面这个例子,在App组件中使用了三个Clock组件:
function App() {
ReactDOM.render(
document.getElementById('root')
& & 每一个Clock都会创建它自己的timer并独立更新。
& & React中的事件处理和Dom的事件处理非常相似。但是还是存在某些不同之处:
React的事件命名规范必须遵守“驼峰原则”。
JSX标签中,我们传递一个function作为事件的处理方法,而不是一个字符串。
& & 例如在html中,处理一个事件:
&button onclick="activateLasers()"&
Activate Lasers
& & 而在React中:
&button onClick={activateLasers}&
Activate Lasers
& & 还有一个区别是,在React 中不能在函数中返回false来阻止React的默认行为,必须明确调用&preventDefault&方法来阻止某些行为。例如在html中,在a标签中为了防止a标签的默认行为打开一个页面,我们可以这样来写:
&a href="#" onclick="console.log('The link was clicked.'); return false"&
& & 而在React实现这个功能,需要这样编码:
function ActionLink() {//定义一个组件
function handleClick(e) {
e.preventDefault();//阻止冒泡行为
console.log('The link was clicked.');
&a href="#" onClick={handleClick}&
& & 在这个例子中,e是一个合成的事件对象实例(event对象),React根据W3C标准合成了事件对象,因此我们不必担心浏览器之间的兼容性问题。可以参阅官网&&了解事件对象的细节。
& & 在使用React时,注册对某个Dom对象的事件监听不需要调用addEventListener&方法,仅仅需要在元素被渲染时(组件的render方法中)提供监听即可。
& & 当我们创建一个组建时,最通常的方法是使用一个事件处理方法来处理对应的事件,看下面这个例子:
class Toggle ponent {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 必须要使用bind方法确保this实例响应回调事件
this.handleClick = this.handleClick.bind(this);
handleClick() {
this.setState(prevState =& ({
isToggleOn: !prevState.isToggleOn
render() {
&button onClick={this.handleClick}&
{this.state.isToggleOn ? 'ON' : 'OFF'}
ReactDOM.render(
&Toggle /&,
document.getElementById('root')
根据条件渲染
& & 在React中可以创建各种各样的组件以满足不同的需求。可以在组件中进行条件判断来觉得组件最终的呈现效果。我们可以按照JavaScript的条件判断方法来实现根据条件渲染组件:
& & 首先创建2个组件:
function UserGreeting(props) {
return &h1&Welcome back!&/h1&;
function GuestGreeting(props) {
return &h1&Please sign up.&/h1&;
& & 然后我们根据输入的参数条件来决定如何输出:
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return &UserGreeting /&;
return &GuestGreeting /&;
ReactDOM.render(
// Try changing to isLoggedIn={true}:
&Greeting isLoggedIn={false} /&,
document.getElementById('root')
& & 可以使用一个变量来存储元素,并根据条件变化来改变渲染的效果。下面是一个登入和登出的例子:
function LoginButton(props) {
&button onClick={props.onClick}&
function LogoutButton(props) {
&button onClick={props.onClick}&
class LoginControl ponent {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
handleLoginClick() {
this.setState({isLoggedIn: true});
handleLogoutClick() {
this.setState({isLoggedIn: false});
render() {
const isLoggedIn = this.state.isLoggedIn;
let button =
if (isLoggedIn) {
button = &LogoutButton onClick={this.handleLogoutClick} /&;
button = &LoginButton onClick={this.handleLoginClick} /&;
&Greeting isLoggedIn={isLoggedIn} /&
ReactDOM.render(
&LoginControl /&,
document.getElementById('root')
& & 在例子中,首先创建了2个无状态的按钮——login、logout。然后在LoginControl中设定了“this.state = {isLoggedIn: false};”。前面我们已经提到过,每当调用setState方法设置状态时,render方法都会被调用并重新渲染Dom,因此在每次点击按钮后都会根据isLoggedIn的状态来决定显示的内容。
版权所有 爱编程 (C) Copyright 2012. . All Rights Reserved.
闽ICP备号-3
微信扫一扫关注爱编程,每天为您推送一篇经典技术文章。致我们终将组件化的 Web - Web前端 - ITeye资讯
相关知识库:
本文转自:
这篇文章将从两年前的一次技术争论开始。争论的聚焦就是下图的两个目录分层结构。我说按模块划分好,他说你傻逼啊,当然是按资源划分。
”按模块划分“目录结构,把当前模块下的所有逻辑和资源都放一起了,这对于多人独自开发和维护个人模块不是很好吗?当然了,那争论的结果是我乖乖地改回主流的”按资源划分“的目录结构。因为,没有做到JS模块化和资源模块化,仅仅物理位置上的模块划分是没有意义的,只会增加构建的成本而已。
虽然他说得好有道理我无言以对,但是我心不甘,等待他日前端组件化成熟了,再来一战!
而今天就是我重申正义的日子!只是当年那个跟你撕逼的人不在。
模块化的不足
模块一般指能够独立拆分且通用的代码单元。由于JavaScript语言本身没有内置的模块机制(ES6有了!!),我们一般会使用CMD或ADM建立起模块机制。现在大部分稍微大型一点的项目,都会使用requirejs或者seajs来实现JS的模块化。多人分工合作开发,其各自定义依赖和暴露接口,维护功能模块间独立性,对于项目的开发效率和项目后期扩展和维护,都是是有很大的帮助作用。
但,麻烦大家稍微略读一下下面的代码
'Tmpl!../tmpl/list.html','lib/qqapi','module/position','module/refresh','module/page','module/net'
], function(listTmpl, QQapi, Position, Refresh, Page, NET){
var foo = '',
QQapi.report();
Position.getLocaiton(function(data){
var init = function(){
NET.get('/cgi-bin/xxx/xxx',function(data){
renderA(data.banner);
renderB(data.list);
var processData = function(){
var bind = function(){
var renderA = function(){
var renderB = function(data){
listTmpl.render('#listContent',processData(data));
var refresh = function(){
Page.refresh();
// app start
上面是具体某个页面的主js,已经封装了像Position,NET,Refresh等功能模块,但页面的主逻辑依旧是”面向过程“的代码结构。所谓面向过程,是指根据页面的渲染过程来编写代码结构。像:init -& getData -& processData -& bindevent -& report -& xxx 。 方法之间线性跳转,你大概也能感受这样代码弊端。随着页面逻辑越来越复杂,这条”过程线“也会越来越长,并且越来越绕。加之缺少规范约束,其他项目成员根据各自需要,在”过程线“加插各自逻辑,最终这个页面的逻辑变得难以维护。
开发需要小心翼翼,生怕影响“过程线”后面正常逻辑。并且每一次加插或修改都是bug泛滥,无不令产品相关人员个个提心吊胆。
页面结构模块化
基于上面的面向过程的问题,行业内也有不少解决方案,而我们团队也总结出一套成熟的解决方案:Abstractjs,页面结构模块化。我们可以把我们的页面想象为一个乐高机器人,需要不同零件组装,如下图,假设页面划分为tabContainer,listContainer和imgsContainer三个模块。最终把这些模块add到最终的pageModel里面,最终使用rock方法让页面启动起来。
下面是伪代码的实现
'Tmpl!../tmpl/list.html','Tmpl!../tmpl/imgs.html','lib/qqapi','module/refresh','module/page'
], function(listTmpl, imgsTmpl, QQapi, Refresh, Page ){
var tabContainer = new RenderModel({
renderContainer: '#tabWrap',
renderTmpl: "&li soda-repeat='item in data.tabs'&{{item}}&/li&",
event: function(){
// tab's event
var listContainer = new ScrollModel({
scrollEl: $.os.ios ? $('#Page') : window,
renderContainer: '#listWrap',
renderTmpl: listTmpl,
cgiName: '/cgi-bin/index-list?num=1',
processData: function(data) {
event: function(){
// listElement's event
error: function(data) {
Page.show('数据返回异常[' + data.retcode + ']');
var imgsContainer = new renderModel({
renderContainer: '#imgsWrap',
renderTmpl: listTmpl,
cgiName: '/cgi-bin/getPics',
processData: function(data) {
event: function(){
// imgsElement's event
complete: function(data) {
QQapi.report();
var page = new PageModel();
page.add([tabContainer,listContainer,imgsContainer]);
page.rock();
我们把这些常用的请求CGI,处理数据,事件绑定,上报,容错处理等一系列逻辑方法,以页面块为单位封装成一个Model模块。
这样的一个抽象层Model,我们可以清晰地看到该页面块,请求的CGI是什么,绑定了什么事件,做了什么上报,出错怎么处理。新增的代码就应该放置在相应的模块上相应的状态方法(preload,process,event,complete…),杜绝了以往的无规则乱增代码的行文。并且,根据不同业务逻辑封装不同类型的Model,如列表滚动的ScrollModel,滑块功能的SliderModel等等,可以进行高度封装,集中优化。
现在基于Model的页面结构开发,已经带有一点”组件化“的味道。每个Model都带有各自的数据,模板,逻辑。已经算是一个完整的功能单元。但距离真正的WebComponent还是有一段距离,至少满足不了我的”理想目录结构“。
WebComponents 标准
我们回顾一下使用一个datapicker的jquery的插件,所需要的步奏:
1.引入插件js
2. 引入插件所需的css(如果有)
3. copy 组件的所需的html片段
4. 添加代码触发组件启动
现阶段的“组件”基本上只能达到是某个功能单元上的集合。他的资源都是松散地分散在三种资源文件中,而且组件作用域暴露在全局作用域下,缺乏内聚性很容易就会跟其他组件产生冲突,如最简单的css命名冲突。对于这种“组件”,还不如上面的页面结构模块化。
于是W3C按耐不住了,制定一个WebComponents标准,为组件化的未来指引了明路。
下面以较为简洁的方式介绍这份标准,力求大家能够快速了解实现组件化的内容。(对这部分了解的同学,可以跳过这一小节)
1. &template&模板能力
模板这东西大家最熟悉不过了,前些年见的较多的模板性能大战artTemplate,juicer,tmpl,underscoretemplate等等。而现在又有mustachejs无逻辑模板引擎等新入选手。可是大家有没有想过,这么基础的能力,原生HTML5是不支持的(T_T)。
而今天WebComponent将要提供原生的模板能力
&template id="datapcikerTmpl"&
&div&我是原生的模板&/div&
&/template&
template标签内定义了myTmpl的模板,需要使用的时候就要innerHTML= document.querySelector('#myTmpl').content;可以看出这个原生的模板够原始,模板占位符等功能都没有,对于动态数据渲染模板能力只能自力更新。
2. ShadowDom 封装组件独立的内部结构
ShadowDom可以理解为一份有独立作用域的html片段。这些html片段的CSS环境和主文档隔离的,各自保持内部的独立性。也正是ShadowDom的独立特性,使得组件化成为了可能。
var wrap = document.querySelector('#wrap');
var shadow = wrap.createShadowRoot();
shadow.innerHTML = '&p&you can not see me &/p&'
在具体dom节点上使用createShadowRoot方法即可生成其ShadowDom。就像在整份Html的屋子里面,新建了一个shadow的房间。房间外的人都不知道房间内有什么,保持shadowDom的独立性。
3. 自定义原生标签
初次接触Angularjs的directive指令功能,设定好组件的逻辑后,一个&Datepicker /&就能引入整个组件。如此狂炫酷炸碉堡天的功能,实在令人拍手称快,跃地三尺。
var tmpl = document.querySelector('#datapickerTmpl');
var datapickerProto = Object.create(HTMLElement.prototype);
// 设置把我们模板内容我们的shadowDom
datapickerProto.createdCallback = function() {
var root = this.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
var datapicker = docuemnt.registerElement('datapicker',{
prototype: datapickerProto
Object.create方式继承HTMLElement.prototype,得到一个新的prototype。当解析器发现我们在文档中标记它将检查是否一个名为createdCallback的方法。如果找到这个方法它将立即运行它,所以我们把克隆模板的内容来创建的ShadowDom。
最后,registerElement的方法传递我们的prototype来注册自定义标签。
上面的代码开始略显复杂了,把前面两个能力“模板”“shadowDom”结合,形成组件的内部逻辑。最后通过registerElement的方式注册组件。之后可以愉快地&datapicker&&/datapicker&的使用。
4. imports解决组件间的依赖
&link rel="import" href="datapciker.html"&
这个类php最常用的html导入功能,HTML原生也能支持了。
WebComponents标准内容大概到这里,是的,我这里没有什么Demo,也没有实践经验分享。由于webComponents新特性,基本上除了高版本的Chrome支持外,其他浏览器的支持度甚少。虽然有polymer帮忙推动webcompoents的库存在,但是polymer自身的要求版本也是非常高(IE10+)。所以今天的主角并不是他。
我们简单来回顾一下WebCompoents的四部分功能:
1 .&template&定义组件的HTML模板能力
2. Shadow Dom封装组件的内部结构,并且保持其独立性
3. Custom Element 对外提供组件的标签,实现自定义标签
4. import解决组件结合和依赖加载
组件化实践方案
官方的标准看完了,我们思考一下。一份真正成熟可靠的组件化方案,需要具备的能力。
“资源高内聚”—— 组件资源内部高内聚,组件资源由自身加载控制
“作用域独立”—— 内部结构密封,不与全局或其他组件产生影响
“自定义标签”—— 定义组件的使用方式
“可相互组合”—— 组件正在强大的地方,组件间组装整合
“接口规范化”—— 组件接口有统一规范,或者是生命周期的管理
个人认为,模板能力是基础能力,跟是否组件化没有强联系,所以没有提出一个大点。
既然是实践,现阶段WebComponent的支持度还不成熟,不能作为方案的手段。而另外一套以高性能虚拟Dom为切入点的组件框架React,在facebook的造势下,社区得到了大力发展。另外一名主角Webpack,负责解决组件资源内聚,同时跟React极度切合形成互补。
所以【】+【】将会是这套方案的核心技术。
不知道你现在是“又是react+webpack”感到失望,还是“太好了是react+webpack”不用再学一次新框架的高兴dd57e48b489e172cb8bd1a7eb08311c1。无论如何下面的内容不会让你失望的。
### 一,组件生命周期
React天生就是强制性组件化的,所以可以从根本性上解决面向过程代码所带来的麻烦。React组件自身有生命周期方法,能够满足“接口规范化”能力点。并且跟“页面结构模块化”的所封装抽离的几个方法能一一对应。另外react的jsx自带模板功能,把html页面片直接写在render方法内,组件内聚性更加紧密。
由于React编写的JSX是会先生成虚拟Dom的,需要时机才真正插入到Dom树。使用React必须要清楚组件的生命周期,其生命周期三个状态:
Mount: 插入Dom
Update: 更新Dom
Unmount: 拔出Dom
mount这单词翻译增加,嵌入等。我倒是建议“插入”更好理解。插入!拔出!插入!拔出!默念三次,懂了没?别少看黄段子的力量。
组件状态就是: 插入-& 更新 -&拔出。
然后每个组件状态会有两种处理函数,一前一后,will函数和did函数。
componentWillMount()& 准备插入前
componentDidlMount()& 插入后
componentWillUpdate() 准备更新前
componentDidUpdate()& 更新后
componentWillUnmount() 准备拔出前
因为拔出后基本都是贤者形态(我说的是组件),所以没有DidUnmount这个方法。
另外React另外一个核心:数据模型props和state,对应着也有自个状态方法
getInitialState()&&&& 获取初始化state。
getDefaultProps() 获取默认props。对于那些没有父组件传递的props,通过该方法设置默认的props
componentWillReceiveProps()& 已插入的组件收到新的props时调用
还有一个特殊状态的处理函数,用于优化处理
shouldComponentUpdate():判断组件是否需要update调用
加上最重要的render方法,React自身带的方法刚刚好10个。对于初学者来说是比较难以消化。但其实getInitialState,componentDidMount,render三个状态方法都能完成大部分组件,不必望而却步。
回到组件化的主题。
一个页面结构模块化的组件,能独立封装整个组件的过程线
我们换算成React生命周期方法:
组件的状态方法流中,有两点需要特殊说明:
1,二次渲染:
由于React的虚拟Dom特性,组件的render函数不需自己触发,根据props和state的改变自个通过差异算法,得出最优的渲染。
请求CGI一般都是异步,所以必定带来二次渲染。只是空数据渲染的时候,有可能会被React优化掉。当数据回来,通过setState,触发二次render
2,componentWiillMount与componentDidMount的差别
和大多数React的教程文章不一样,ajax请求我建议在WillMount的方法内执行,而不是组件初始化成功之后的DidMount。这样能在“空数据渲染”阶段之前请求数据,尽早地减少二次渲染的时间。
willMount只会执行一次,非常适合做init的事情。
didMount也只会执行一次,并且这时候真实的Dom已经形成,非常适合事件绑定和complete类的逻辑
### 二,JSX很丑,但是组件内聚的关键!
WebComponents的标准之一,需要模板能力。本是以为是我们熟悉的模板能力,但React中的JSX这样的怪胎还是令人议论纷纷。React还没有火起来的时候,大家就已经在微博上狠狠地吐槽了“JSX写的代码这TM的丑”。这其实只是Demo阶段JSX,等到实战的大型项目中的JSX,包含多状态多数据多事件的时候,你会发现………….JSX写的代码还是很丑。
(即使用sublime-babel等插件高亮,逻辑和渲染耦合一起,阅读性还是略差)
为什么我们会觉得丑?因为我们早已经对“视图-样式-逻辑”分离的做法潜移默化。
基于维护性和可读性,甚至性能,我们都不建议直接在Dom上面绑定事件或者直接写style属性。我们会在JS写事件代理,在CSS上写上classname,html上的就是清晰的Dom结构。我们很好地维护着MVC的设计模式,一切安好。直到JSX把他们都糅合在一起,所守护的技术栈受到侵略,难免有所抵制。
但是从组件化的目的来看,这种高内聚的做法未尝不可。
下面的代码,之前的“逻辑视图分离”模式,我们需要去找相应的js文件,相应的event函数体内,找到td-info的class所绑定的事件。
对比起JSX的高度内聚,所有事件逻辑就是在本身jsx文件内,绑定的就是自身的showInfo方法。组件化的特性能立马体现出来。
&p className="td-info" onClick={this.showInfo}&{}&/p&
(注意:虽然写法上我们好像是HTML的内联事件处理器,但是在React底层并没有实际赋值类似onClick属性,内层还是使用类似事件代理的方式,高效地维护着事件处理器)
再来看一段style的jsx。其实jsx没有对样式有硬性规定,我们完全可遵循之前的定义class的逻辑。任何一段样式都应该用class来定义。在jsx你也完全可以这样做。但是出于组件的独立性,我建议一些只有“一次性”的样式直接使用style赋值更好。减少冗余的class。
&div className="list" style={{background: "#ddd"}}&
{list_html}
或许JSX内部有负责繁琐的逻辑样式,可JSX的自定义标签能力,组件的黑盒性立马能体验出来,是不是瞬间美好了很多。
render: function(){
&Menus bannerNums={this.state.list.length}&&/Menus&
&TableList data={this.state.list}&&/TableList&
虽然JSX本质上是为了虚拟Dom而准备的,但这种逻辑和视图高度合一对于组件化未尝不是一件好事。
学习完React这个组件化框架后,看看组件化能力点的完成情况
“资源高内聚”—— (33%)& html与js内聚
“作用域独立”—— (50%)& js的作用域独立
“自定义标签”—— (100%)jsx
“可相互组合”—— (50%)& 可组合,但缺乏有效的加载方式
“接口规范化”—— (100%)组件生命周期方法
### Webpack 资源组件化
对于组件化的资源独立性,一般的模块加载工具和构建流程视乎变得吃力。组件化的构建工程化,不再是之前我们常见的,css合二,js合三,而是体验在组件间的依赖于加载关系。webpack正好符合需求点,一方面填补组件化能力点,另一方帮助我们完善组件化的整体构建环境。
首先要申明一点是,webpack是一个模块加载打包工具,用于管理你的模块资源依赖打包问题。这跟我们熟悉的requirejs模块加载工具,和grunt/gulp构建工具的概念,多多少少有些出入又有些雷同。
首先webpak对于CommonJS与AMD同时支持,满足我们模块/组件的加载方式。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someV
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
return someExportedV
当然最强大的,最突出的,当然是模块打包功能。这正是这一功能,补充了组件化资源依赖,以及整体工程化的能力
根据webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,可以把想css,图片等资源等有依赖关系的“模块”加载。这跟我们使用requirejs这种仅仅处理js大大不同。而这套加载机制,通过一个个loader来实现。
// webpack.config.js
module.exports = {
entry: './index.jsx',
path: __dirname,
filename: '[name].min.js'
loaders: [
{test: /\.css$/, loader: 'style!css' },
{test: /\.(jsx|js)?$/, loader: 'jsx?harmony', exclude: /node_modules/},
{test: /\.(png|jpg|jpeg)$/, loader: 'url-loader?limit=10240'}
上面一份简单的webpack配置文件,留意loaders的配置,数组内一个object配置为一种模块资源的加载机制。test的正则为匹配文件规则,loader的为匹配到文件将由什么加载器处理,多个处理器之间用!分隔,处理顺序从右到左。
如style!css,css文件通过css-loader(处理css),再到style-loader(inline到html)的加工处理流。
jsx文件通过jsx-loader编译,‘?’开启加载参数,harmony支持ES6的语法。
图片资源通过url-loader加载器,配置参数limit,控制少于10KB的图片将会base64化。
#### 资源文件如何被require?
// 加载组件自身css
require('./slider.css');
// 加载组件依赖的模块
var Clip = require('./clipitem.js');
// 加载图片资源
var spinnerImg = require('./loading.png');
在webpack的js文件中我们除了require我们正常的js文件,css和png等静态文件也可以被require进来。我们通过webpack命令,编译之后,看看输出结果如何:
webpackJsonp([0], {
/***/ function(module, exports, __webpack_require__) {
// 加载组件自身css
__webpack_require__(1);
// 加载组件依赖的模块
var Clip = __webpack_require__(5);
// 加载图片资源
var spinnerImg = __webpack_require__(6);
/***/ function(module, exports, __webpack_require__) {
/***/ function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(3)();
exports.push([module.id, ".slider-wrap{\r\n position:\r\n width: 100%;\r\n margin: 50\r\n background: #\r\n}\r\n\r\n.slider-wrap li{\r\n text-align:\r\n line-height: 20\r\n}", ""]);
/***/ function(module, exports) {
/***/ function(module, exports, __webpack_require__) {
/***/ function(module, exports) {
console.log('hello, here is clipitem.js') ;
/***/ function(module, exports) {
module.exports = "data:image/base64,iVBORw0KGg......"
webpack编译之后,输出文件视乎乱糟糟的,但其实每一个资源都被封装在一个函数体内,并且以编号的形式标记(注释)。这些模块,由webpack的__webpack_require__内部方法加载。入口文件为编号0的函数index.js,可以看到__webpack_require__加载其他编号的模块。
css文件在编号1,由于使用css-loader和style-loader,编号1-4都是处理css。其中编号2我们可以看我们的css的string体。最终会以内联的方式插入到html中。
图片文件在编号6,可以看出exports出base64化的图片。
#### 组件一体输出
// 加载组件自身css
require('./slider.css');
// 加载组件依赖的模块
var React = require('react');
var Clip = require('../ui/clipitem.jsx');
// 加载图片资源
var spinnerImg = require('./loading.png');
var Slider = React.createClass({
getInitialState: function() {
componentDidMount: function(){
render: function() {
&Clip data={this.props.imgs} /&
&img className="loading" src={spinnerImg} /&
module.exports = S
如果说,react使到html和js合为一体。
那么加上webpack,两者结合一起的话。js,css,png(base64),html 所有web资源都能合成一个JS文件。这正是这套方案的核心所在:组件独立一体化。如果要引用一个组件,仅仅require('./slider.js') 即可完成。
加入webpack的模块加载器之后,我们组件的加载问题,内聚问题也都成功地解决掉
“资源高内聚”—— (100%) 所有资源可以一js输出
“可相互组合”—— (100%)& 可组合可依赖加载
### CSS模块化实践
很高兴,你能阅读到这里。目前我们的组件完成度非常的高,资源内聚,易于组合,作用域独立互不污染。。。。等等eee,视乎CSS模块的完成度有欠缺。
那么目前组件完成度来看,CSS作用域其实是全局性的,并非组件内部独立。下一步,我们要做得就是如何让我们组件内部的CSS作用域独立。
这时可能有人立马跳出,大喊一句“德玛西亚!”,哦不,应该是“用sass啊傻逼!”。可是项目组件化之后,组件的内部封装已经很好了,其内部dom结构和css趋向简单,独立,甚至是破碎的。LESS和SASS的一体式样式框架的设计,他的嵌套,变量,include,函数等丰富的功能对于整体大型项目的样式管理非常有效。但对于一个功能单一组件内部样式,视乎就变的有点格格不入。“不能为了框架而框架,合适才是最好的”。视乎原生的css能力已经满足组件的样式需求,唯独就是上面的css作用域问题。
这里我给出思考的方案: classname随便写,保持原生的方式。编译阶段,根据组件在项目路径的唯一性,由【组件classname+组件唯一路径】打成md5,生成全局唯一性classname。正当我要写一个loader实现我的想法的时候,发现歪果仁已经早在先走一步了。。。。
这里具体方案参考我之前博客的译文:/6/
之前我们讨论过JS的模块。现在通过Webpack被加载的CSS资源叫做“CSS模块”?我觉得还是有问题的。现在style-loader插件的实现本质上只是创建link[rel=stylesheet]元素插入到document中。这种行为和通常引入JS模块非常不同。引入另一个JS模块是调用它所提供的接口,但引入一个CSS却并不“调用”CSS。所以引入CSS本身对于JS程序来说并不存在“模块化”意义,纯粹只是表达了一种资源依赖——即该组件所要完成的功能还需要某些asset。
因此,那位歪果仁还扩展了“CSS模块化”的概念,除了上面的我们需要局部作用域外,还有很多功能,这里不详述。具体参考原文
非常赞的一点,就是cssmodules已经被css-loader收纳。所以我们不需要依赖额外的loader,基本的css-loader开启参数modules即可。
//webpack.config.js
loaders: [
{test: /\.css$/, loader: 'style!css?modules&localIdentName=[local]__[name]_[hash:base64:5]' },
modules参数代表开启css-modules功能,loaclIdentName为设置我们编译后的css名字,为了方便debug,我们把classname(local)和组件名字(name)输出。当然可以在最后输出的版本为了节省提交,仅仅使用hash值即可。另外在react中的用法大概如下。
var styles = require('./banner.css');
var Banner = new React.createClass({
render: function(){
&div className={styles.classA}&&/div&
最后这里关于出于对CSS一些思考,
关于css-modules的其它功能,我并不打算使用。在内部分享【我们竭尽所能地让CSS变得复杂】中提及:
&&&&&&& 引用我们项目中大部分的CSS都不会像boostrap那样需要变量来设置,身为一线开发者的我们大概能够感受到:设计师们改版UI,绝对不是简单的换个色或改个间距,而是面目全非的全新UI,这绝对不是一个变量所能解决的”维护性“。
&&&&&&& 反而项目实战过程中,真正要解决的是:在版本迭代过程中那些淘汰掉的过期CSS,大量地堆积在项目当中。我们像极了家中的欧巴酱不舍得丢掉没用的东西,因为这可是我们使用sass或less编写出具有高度的可维护性的,肯定有复用的一天。
&&&&&&&& 这些堆积的过期CSS(or sass)之间又有部分依赖,一部分过期没用了,一部分又被新的样式复用了,导致没人敢动那些历史样式。结果现网项目迭代还带着大量两年前没用的样式文件。
组件化之后,css的格局同样被革新了。可能postcss才是你现在手上最适合的工具,而不在是sass。
到这里,我们终于把组件化最后一个问题也解决了。
“作用域独立”—— (100%) 如同shadowDom作用域独立
到这里,我们可以开一瓶82年的雪碧,好好庆祝一下。不是吗?
### 组件化之路还在继续
webpack和react还有很多新非常重要的特性和功能,介于本文仅仅围绕着组件化的为核心,没有一一阐述。另外,配搭gulp/grunt补充webpack构建能力,webpack的codeSplitting,react的组件通信问题,开发与生产环境配置等等,都是整套大型项目方案的所必须的,限于篇幅问题。可以等等我更新下篇,或大家可以自行查阅。
但是,不得不再安利一下react-hotloader神器。热加载的开发模式绝对是下一代前端开发必备。严格说,如果没有了热加载,我会很果断地放弃这套方案,即使这套方案再怎么优秀,我都讨厌react需要5~6s的编译时间。但是hotloader可以在我不刷新页面的情况下,动态修改代码,而且不单单是样式,连逻辑也是即时生效。
如上在form表单内。使用热加载,表单不需要重新填写,修改submit的逻辑立刻生效。这样的开发效率真不是提高仅仅一个档次。必须安利一下。
或许你发现,使用组件化方案之后,整个技术栈都被更新了一番。学习成本也不少,并且可以预知到,基于组件化的前端还会很多不足的问题,例如性能优化方案需要重新思考,甚至最基本的组件可复用性不一定高。后面很长一段时间,需要我们不断磨练与优化,探求最优的前端组件化之道。
至少我们可以想象,不再担心自己写的代码跟某个谁谁冲突,不再为找某段逻辑在多个文件和方法间穿梭,不再copy一片片逻辑然后改改。我们每次编写都是可重用,可组合,独立且内聚的组件。而每个页面将会由一个个嵌套组合的组件,相互独立却相互作用。
对于这样的前端未来,有所期待,不是很好吗
至此,感谢你的阅读。
前端的水看来也挺深
每过18个月,前端的难度就会增加一倍
读完了 还好这么幽默
这是个伪命题&&

我要回帖

更多关于 中兴天机axon顶配版 的文章

 

随机推荐