整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

秒懂ReactJS

秒懂ReactJS

篇文章是为ReactJs小白准备的,希望他们快速抓住ReactJs的要点并能在实践中随机应变。

两句话版本

  • ReactJs把视图更新简化为一个render函数
  • render函数接收两个参数,分别是配置项和状态

长版本

ReactJs是一个专注于View的Web前端框架。Web前端的View就是浏览器中的Dom元素,改变View的唯一途径就是修改浏览器中的Dom元素,因此ReactJs的核心任务就是如何修改Dom元素,作为一个成功的框架,ReactJs使修改Dom元素变得高效而又简单。

ReactJs把修改Dom的操作简化成一个函数renderInto(parentDom, props, states)=>htmlString,这个函数的意图就是根据props,states计算出视图对应的html字符串并添加为parentDom的子节点。props和states就是普通的javascript对象,这个函数的核心逻辑就是计算html元素的机构及元素属性然后拼接成字符串返回。作为框架,ReactJs用JSX形式的DSL解决了拼接html的任务并接管了更新到parentDom的职责。看一个例子,理解这个函数并理解ReactJs怎么使用这个函数你就可以一个人开始ReactJs之旅了。

var props={name: 'myname'};  
var states={amount: 1000};  


function render(props, states) {  
  var title=’Hello, ' + props.name;  
  var description='You have ' + states.amount + ' score now.';  


  return (  
    <div className="score-board">  
       <h1>{title}</h1>  
       <p>{description}</p>  
    </div>  
  );  
}

函数第一行根据props计算title,第二行根据states计算description,最后以JSX形式返回拼接好的html字符串。

如果你用过AngularJs,EmberJs等类似的前端框架,你可能会觉得没什么了不起,不就是把模板和逻辑放到一起吗?是的,没错,但这不仅仅是组织形式上的改变,而是编程隐喻的转变—从复杂的MVC或MVVM模式到简单的render函数。还有一点不同是JSX最终编译成调用react-dom的javascript语句,而不是直接生成字符串。

render函数还只是ReactJs这座冰山的一角,”React”会在render函数的输入变化时再次调用这个函数。再看一个例子。

var props={name: 'myname'};  
var states={amount: 1000};  


function handleClickAdd() {  
  states={amount: states.amount + 1};  
}  


function render(props, states) {  
  var title=’Hello, ' + props.name;  
  var description='You have ' + states.amount + ' score now.';  


  return (  
    <div className="score-board">  
       <h1>{title}</h1>  
       <p>{description}</p>  
       <button onClick={handleClickAdd}>+1</button>  
    </div>  
  );  
}

这个例子增加了一个”+1”按钮,当用户点击按钮时会修改states,ReactJs在states变化时的”React”就是再次调用render函数,然后用新输出更新浏览器的dom。

可能你会问,props和states不就是Model吗?是的,可以理解成Model,但此Model非彼Model,props和states都是为View服务的而非和View平起平坐。

可能你还会问,为啥不把props和states合并成一个对象?要回答这个问题,就涉及到复杂视图的场景。想想看,当视图内的元素不断增加时,代码上如何处理,还要在一个render函数里折腾吗?肯定不会。我猜你已经想到了,要把大问题拆小。ReactJs给出的解决方法就是把大视图拆成若干个小视图,每个视图都有自己的render函数,在JSX中可以直接使用视图标签。看一个例子。

var Score=React.createClass({  
  initialState: function() {  
    return {amount: 1000};  
  },  


  function handleClickAdd() {  
    this.setState({amount: this.states.amount + 1});  
  }  


  render: function() {  
    var title=’Hello, ' + this.props.name;  
    var description='You have ' + this.states.amount + ' score now.';  


    return (  
      <div className="score-board">  
         <h1>{title}</h1>  
         <p>{description}</p>  
         <button onClick={handleClickAdd}>+1</button>  
      </div>  
    );  
  }  
});  


var ScoreList=React.createClass({  
  render() {  
    return (  
      <ul className="score-list">  
        <li><Score name="Tom" /></li>  
        <li><Score name="Jerry" /></li>  
      </ul>  
    );  
  }  
});  


ReactDOM.render(  
  <ScoreList />,  
  document.getElementById('content')  
);

这个例子中有两类View,分别是Score和ScoreList。ScoreList的render函数中使用Score标签并给出配置项name的值。详细看一下Score,ReactJs提供createClass方法定义视图,在render函数中通过this.props访问外部传入的配置项,通过this.states访问视图内部的状态。从意图上看,props外部传入视图的配置项,拥有者是父视图,视图内部只能读取配置项,states的拥有者是视图自身。

区分props和states的结果就是,子视图没办法直接改变父视图,视图改变一定是自触发改变的视图开始向子视图传播。对上面的例子,当Tom的Score改变时,ScoreList其他部分一定不会改变,所以视图更新从Tom的Score视图开始就可以,这就保证了能更高效地计算视图变化,再加上VirtualDom的使用,使ReactJs的效率大大超过其他框架。

当子视图需要改变父视图时,也一定是从父视图开始向下更新。假如上面的例子中ScoreList还有平均分的视图,当Tom的分数改变时,需要更新ScoreList中的平均分。这就需要Score视图在处理”+1”输入时把变化通知到ScoreList,做法时给Score增加配置项,ScoreList中定义更新平均分的函数并把函数作为配置项传给Score。当ScoreList更新时,因为Jerry的props和states都没变化,所以Jerry的Score视图不需要更新。

这就是ReactJs的全部秘密了(不过Web前端本身是一个复杂系统,你还需要了解更多其他内容)。

ngularJS的主要组成部分是:

  • 启动(startup) - 展示“hello world!”
  • 执行期(runtime) - AngularJS 执行期概览
  • 作用域(scope) - 视图和控制器的集合区
  • 控制器(controller) - 应用的行为
  • 模型(model) - 应用的数据
  • 视图(view) - 用户能看到的
  • 指令(directives) - 扩展HTML语法
  • 过滤器(filters) - 数据本地化
  • 注入器(injector) - 聚合你的应用
  • 模块(module) - 配置注入器
  • $ - AngularJS的命名空间(namespace)

启动

我们通过一个例子来讲解启动这个部分

<!doctype html>

<html ng-app>

<head>

<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>

</head>

<body>

<p ng-init=" name='World' ">Hello {{name}}!</p>

</body>

</html>

  1. 浏览器载入HTML,然后把它解析成DOM树。
  2. 浏览器载入angular.js脚本。
  3. AngularJS等到DOMContentLoaded事件触发执行。
  4. AngularJS寻找ng-app指令,这个指令指示了应用程序的边界。
  5. 使用ng-app中指定的模块来配置注入器($injector)。
  6. 注入器($injector)是用来创建“编译服务($compile service)”和“根作用域($rootScope)”的。
  7. 编译服务($compile service)是用来编译DOM树并把它链接到根作用域($rootScope)的,这里的根作用域就是html。
  8. ng-init指令将“World”赋给作用域里的name这个变量。
  9. 作用域中的name与页面上的{{name}}绑定,整个表达式变成了“Hello World”。

执行期

浏览器的事件机制:

  1. 浏览器的Event loop等待事件的触发。所谓事件包括用户的交互操作、定时事件、或者网络事件(服务器的响应)。
  2. 事件触发后,如果有绑定事件回调函数,那么此函数就会被执行。此时会进入Javascript上下文。通常回调用来修改DOM结构。
  3. 一旦回调执行完毕,浏览器就会离开Javascript上下文,并且根据DOM的修改重新渲染视图。

而AngularJS通过使用自己的Event loop,改变了传统的Javascript工作流。这使得Javascript的执行被分成原生部分和拥有AngularJS执行上下文的部分。只有在AngularJS执行上下文中运行的操作,才能享受到AngularJS提供的数据绑定,异常处理,资源管理等功能和服务。你可以使用 $apply()方法,从普通Javascript上下文进入AngularJS执行上下文。记住,大部分情况下(如在控制器,服务中),$apply都已经被执行过了。只有当你使用自定义的事件回调或者是使用第三方类库的回调时,才需要自己执行$apply。

下面通过一个例子来讲解如何实现“将用户输入绑定到视图上”的效果。

<!doctype html>

<html ng-app>

<head>

<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>

</head>

<body>

<input ng-model="name">

<p>Hello {{name}}!</p>

</body>

</html>

在编译阶段:

input元素上的ng-model指令会给<input>输入框绑定keydown事件;

{{name}}这个变量替换表达式建立了一个 $watch ,来接受 name 变量改变的通知。

在执行期阶段:

按下任何一个键(以X键为例),都会触发一个 input 输入框的keydown事件;

input 上的指令捕捉到 input 内容的改变,然后调用 $apply("name='X';")来更新处于AngularJS执行上下文中的模型;

AngularJS将 name='X'应用到模型上;

$digest 循环开始;这个循环是由两个小循环组成的,这两个小循环用来处理$evalAsync队列和$watch列表。这个$digest循环直到模型“稳定”前会一直迭代。这个稳定具体指的是$evalAsync列表为空,并且$watch列表中检测不到任何改变了。这个$evalAsync队列是用来管理那些“视图渲染前需要在当前栈外执行的操作”。这通常使用 setTimeout(0)来完成的。并且,因为浏览器会根据事件队列按顺序渲染视图,这时还会造成视图的抖动。$watch列表是一个表达式的集合,这些表达式可能是自上次迭代后发生了改变的。如果检测到了有改变,那么$watch函数就会被调用,它通常会把新的值更新到DOM中。

$watch 列表检测到了name值的变化,然后通知 {{name}}变量替换的表达式,这个表达式负责将DOM进行更新;

AngularJS退出执行上下文,然后退出Javascript上下文中的keydown事件;

浏览器以更新的文本重新渲染视图。

作用域(Scope)

作用域是用来检测模型的改变和为表达式提供执行上下文的。它是分层组织起来的,并且层级关系是紧跟着DOM的结构的。

下面这个例子演示了{{name}}表达式在不同的作用域下被解析成了不同的值

<!doctype html>

<html ng-app>

<head>

<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>

<script>

function GreetCtrl($scope) {

      $scope.name='World';

     }

     function ListCtrl($scope) {

      $scope.names=['Igor', 'Misko', 'Vojta'];

     }

</script>

</head>

<body>

<div ng-controller="GreetCtrl">

Hello {{name}}!

</div>

<div ng-controller="ListCtrl">

<ol>

<li ng-repeat="name in names">{{name}}</li>

</ol>

</div>

</body>

</html>

在GreetCtrl控制器中的name等于'World'。在ListCtrl控制器中的name等于'Igor', 'Misko', 'Vojta'。因为它们的作用域不一样。

控制器

视图背后的控制代码就是控制器。它的主要工作内容是构造模型和回调方法,并把模型和回调方法一起发送到视图。 视图可以看做是作用域在模板(HTML)上的投影。而作用域是一个中间地带,它把模型整理好传递给视图,把浏览器事件传递给控制器。控制器和视图的分离非常重要,因为:

(1)控制器是由Javascript写的。Javascript是命令式的,命令式的语言适合用来编写应用的行为。控制器不应该包含任何关于渲染代码(DOM引用或者片段)。

(2)视图模板是用HTML写的。HTML是声明是的,声明式的语言适合用来编写UI。视图不应该包含任何行为。

(3)因为控制器和视图没有直接的调用关系,所以可以使多个视图对应同一个控制器。这对“换肤(re-skinning)”、适配不同设备(比如移动设备和台式机)、测试,都非常重要。

模型

模型就是用来和模板结合生成视图的数据。模型在作用域中可以被引用,这样才能被渲染生成视图。和其他框架不一样的是,Angularjs对模型本身没有任何限制和要求。你不需要继承任何类也不需要实现指定的方法。 模型可以是哈希形式的原生对象,也可以是完整对象类型。简而言之,模型可以是原生的Javascript对象。

视图

所谓视图,就是指用户所看见的。 视图的生命周期由作为一个模板开始,它将和模型合并,并最终渲染到浏览器的DOM中。与其他模板系统不同的是,AngularJS使用一种独特的形式来渲染视图。

其他模板 - 大部分模板系统工作原理,都是一开始获取一个带有特殊标记的HTML形式字符串。通常情况下模板的特殊标记破坏了HTML的语法,以至于模板是不能用HTML编辑器编辑的。然后这个字符串会被送到模板引擎那里解析,并和数据合并。合并的结果是一个可以被浏览器解析的HTML字符串。这个字符串会被.innerHTML方法写到DOM中。使用innerHTML会造成浏览器的重新渲染。当模型改变时,这整个流程又要重复一遍。模板的生存周期就是DOM的更新周期。这里我想强调是,这些模板的基础是字符串。

AngularJS - AngularJS和其它模板系统不同。它使用的是DOM而不是字符串。模板仍然是用HTML字符串写的,并且它仍然是HTML。浏览器将它解析成DOM, 然后这个DOM会作为输入传递给模板引擎,也就是我们的编译器。编译器查看其中的指令,找到的指令后,会开始监视指令内容中相应的模型。 这样做,就使得视图能“连续地”更新,不需要模板和数据的重新合并。你的模型也就成了你视图变化的唯一原因。

指令

一个指令 就是一种“由某个属性、元素名称、css类名出现而导致的行为,或者说是DOM的变化”。指令能让你以一种声明式的方法来扩展HTML表示能力。

Filters过滤器

过滤器扮演着数据翻译的角色。一般他们主要用在数据需要格式化为本地格式的时候。它参照了UNIX过滤的规则,并且也实现了“|”(管道)语法。

模块和注入器

每个AngularJS应用都有一个唯一的注入器。注入器提供一个通过名字查找对象实例的方法。它将所有对象缓存在内部,所以如果重复调用同一名称的对象,每次调用都会得到同一个实例。如果调用的对象不存在,那么注入器就会让实例的工厂(instance factory)函数创建一个新的实例。

一个模块就是一种配置注入器的实例的工厂函数的方式,我们也称它为“提供者(provider)”。

var myModule=angular.module('myModule', [])

myModule.factory('serviceA', function() { //定义serviceA的工厂函数,myModule模块就是提供serviceA实例的工厂函数的提供者

return {

......

};

});

// create an injector and configure it from 'myModule'

var $injector=angular.injector('myModule');

// retrieve an object from the injector by name

var serviceA=$injector.get('serviceA'); //从注入器查找serviceA对象,这时注入器会让实例serviceA的工厂函数factory创建一个新的实例serviceA返回

// always true because of instance cache

$injector.get('serviceA')===$injector.get('serviceA');

注入器真正强大之处在于让方法和类型能够通过注入器,请求到他们依赖的组件,而不需要自己加载依赖。

我们看看下面动态时间的这个例子:

<!doctype html>

<html ng-app="timeExampleModule">

<head>

<script src="http://code.angularjs.org/angular-1.1.0.min.js"></script>

<script">

  angular.module('timeExampleModule', []).

      factory('time', function($timeout) {

       var time={};

      (function tick() {

      time.now=new Date().toString();

      $timeout(tick, 1000);

      })();

      return time;

     });

    function ClockCtrl($scope, time) {

     $scope.time=time;

    }

</script>

</head>

<body>

<div ng-controller="ClockCtrl">

Current time is: {{ time.now }}

</div>

</body>

</html>

你只要把需要的依赖写在函数参数里。当AngularJS调用这个函数时,它会自动填充好需要的参数。这个例子中,当ng-controller实例化构造器ClockCtrl的时候,它自动提供了指明的依赖time实例对象。

AngularJS 命名空间

为了防止意外的命名冲突, AngularJS为可能冲突的对象名加以前缀"$"。所以请不要在你自己的代码里用"$"做前缀,以免和AngularJS代码发生冲突。

于一个Web前端工程师而言,框架知识非常重要,它对你项目的成功有着相当大的影响。专业的郑州Web前端课程都会涵盖JS框架知识,比如Angular和React,今天千锋郑州老师就给大家讲解一下AngularJS与ReactJS的区别,以便同学们理解记忆。

AngularJS:框架领域的冠军

Angular.js是一个开源的Web应用程序框架,具有由Google提供的Model-View-Controller(MVC)架构(Angular 1)和Model-View-ViewModel(MVVM)架构(Angular 2)。Angular.js通过使用指令扩展HTML的功能来解决开发SPA(单页应用程序)的问题,此框架强调让你的app快速完成和运行。目前Angular是最受欢迎的JS框架,你可以一站式使用,它是大型企业的首选框架。

ReactJS:在块上的新生儿

ReactJS是一个开源的JavaScript库,用于构建高性能的用户界面,专注于由Facebook引入和提供的惊人的渲染性能。React专注于模型视图控制器(Model View Controller)架构中的“V”,它是为了解决与其他JavaScript框架的常见问题——大数据集的高效渲染而创建的。React是最轻量级的JS框架,如果你需要逐渐现代化现有的代码库,那么这是一个合适的选择。

Angular和React功能比较:

动态UI绑定:Angular允许在纯对象或甚至属性级别使用UI绑定。可以同时更新多个绑定,而不需要耗时的DOM更新;ReactJS直截了当地将状态直接链接到UI。状态参数作为对象传递,并合并到React组件的内部参考状态。

可重复使用的组件:Angular组件称为“指令”,它们比Ember组件强大得多。它们能够创建你自己语义的和可重用的HTML语法;React在视图和控制器级别使用mixin,因此组件不必UI相关,并且可能只包含一些实用程序或甚至复杂的程序逻辑。

路由:Angular需要模板或控制器到其路由器配置,必须手动管理;React不处理路由,但是有很多模块用于路由,如react-router,flow-router。

意见:Angular和React都是灵活的意见,给出一点灵活性来实现你自己的客户端堆栈。

数据绑定:Angular是双向绑定,React是单向绑定。

要选择哪个框架,需要评估应用程序的需求以及每个框架的优势,需要深入了解所考虑的每个框架的优点和缺点,以及它们如何在不同用例下竞争。如果你正在决策创建一个Web app,对于长期支持和活跃的社区,Angular、React都是安全的。

如果你想了解更多Web前端课程内容,可以关注“千锋郑州校区”微信公众号。如果你想参加专业的郑州Web前端培训班,可以来千锋申请长达两周的免费试听,亲身体验教学效果!