GithubHelp home page GithubHelp logo

jabez128.github.io's Introduction

jabez128.github.io

Programming and Thinking

jabez128.github.io's People

Contributors

jabez128 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jabez128.github.io's Issues

50行代码打造virtual dom

自从React开始在前端圈大火以后,virtual dom作为React的一个重要部分也开始为被大多数前端开发者所熟知。目前,除了React框架本身的内部实现之外,还有一下几个比较成熟的(github星星比较多的)库实现了virtual dom:

初次接触virtual-dom这个概念的时候,我猜很大一部分人都会被这个“高级”的概念所吓到,然后望 virtual-dom 而却步,毕竟不是所有人都有心情和精力去阅读React源码。但是事实上,virtual-dom是并不是什么神秘的黑科技,用一句话就可以总结virtual-dom:

virtual-dom是真实DOM的一个映射

这个看似简单的总结听起来也不像是什么人话。我们可以更加用更加形象的方法来进行讲解,比如下面这段HTML代码:

  <div>
    <span></span>
    <span></span>
  </div>

一个div标签里面包含着两个span标签,两个span标签是并列关系。假如div是一个鸡蛋灌饼,span是一根烤肠,那么我们就可以把上面的这段真实的DOM映射成一个夹着两根烤肠的鸡蛋灌饼,这个豪华版鸡蛋灌饼就可以看做是一个virtual-dom。

当然,我们在写代码的时候并没有那么多的鸡蛋灌饼,当然也没有烤肠,我们有的只是JavaScript代码。我们可以想一想,在Javascript中用什么结构可以和DOM的定义和结构更加贴近呢?答案就是对象字面量。对象字面量表示方法可以设定属性,我们可以用其中的一个属性来记录DOM标签的类型,另一个属性来记录对象本身的一些属性,通过对象的相互嵌套也可以很好的表示DOM的层级结构。

学点无用技能系列 -- 我是前端,我想搞搞docker

先来一只可爱的小鲸鱼镇楼。

docker

docker容器技术目前已经被程序员所广泛使用了,但是作为一个前端切图仔,docker还只是一个“最熟悉”的陌生人,一直闻其名却不知其底细。虽然我知道可能已经有人微笑着骂了我一句:cliche,但是在和公司运维撕了几次逼之后,我还是萌发了学习docker的念头,没准学会了以后就可以和运维更愉快的撕逼了呢啦啦啦

学习目标:

  • 如何(在Mac本上)安装docker
  • docker基础姿势
  • 如何使用docker部署一个nodejs应用
  • 如何在docker里面调试nodejs应用
  • 限于姿势水平,其他的暂时没想到

“指尖上的魔法” -- 谈谈React-Native中的手势

modern_business_icon_tap_gesture-f

React-Native是一款由Facebook开发并开源的框架,主要卖点是使用JavaScript编写原生的移动应用。从2015年3月份开源到现在,已经差不多有半年。目前,React-Native正在以几乎每周一个版本的速度进行快速迭代,开源社区非常活跃。2015年9月15日,React-Native正式宣布支持安卓,并在项目主页中更新了相关文档,这意味着React-Native已经完全覆盖了目前主流的iOS和Android系统,做到了“learn once,write everywhere”。React-Native能否颠覆传统的APP开发方式,现在下结论还为时尚早,但从微博和Twitter对React-Native相关消息的转发数量和评论来看,React-Native在未来的一段时间内都将是移动开发的热点。

从我自己的实际使用经历出发,在使用React-Native写了几个Demo之后,我觉得React-Native是一个非常有前途的框架。虽然目前文档并不能做到面面俱到,实际使用过程中坑也略多,但是填坑的速度也非常快。在项目的issues中提一个issue,基本都能在几个小时之内获得解答或者解决方案。因此,我决定比较系统的学习一下React-Native。

在移动应用开发中,手势是不可忽视的一个重要组成部分,React-Native针对应用中的手势处理,提供了gesture responder system,从最基本的点击手势,到复杂的滑动,都有现成的解决方案。

和以往的Hybrid应用相比,使用React-Native开发的原生应用的一大优势就是可以流畅的响应用户的手势操作,这也是使用React-Native相比以往在原生应用中插入webview控件的一个优势,因此,相比web端的手势,React-Native应用中的手势要复杂得多。我在初次接触React-Native手势之初也是看的一头雾水,经过搜索也发现相关的资料比较少,因此萌发了写一篇相关文章的想法。这也是写作本文的初衷,一方面总结自己学习和摸索的经验,以作为后来使用中的备忘录,另一方面也作为交流分享之用。

Touch*手势

移动应用中最简单的手势,就是touch手势,而这也是应用中最常使用的手势,类比web开发中的事件,就好比web开发中的click。在web开发中,浏览器内部实现了click事件,我们可以通过onclick或者addEventListener('click',callback)来绑定click事件。React-Native也针对Touch手势进行了类似的实现,在React-Native中,一共有四个和Touch相关的组件:

  • TouchableHighlight
  • TouchableNativeFeedback
  • TouchableOpacity
  • TouchableWithoutFeedback

使用这四个组件,我们就可以在应用的某个部分绑定上Touch事件,来个简单的例子:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TouchableHighlight
} = React;

var gesture = React.createClass({
  _onPressIn(){
    this.start = Date.now()
    console.log("press in")
  },
  _onPressOut(){
    console.log("press out")
  },
  _onPress(){
    console.log("press")
  },
  _onLonePress(){
    console.log("long press "+(Date.now()-this.start))
  },
  render: function() {
    return (
      <View style={styles.container}>
        <TouchableHighlight
          style={styles.touchable}
          onPressIn={this._onPressIn}
          onPressOut={this._onPressOut}
          onPress={this._onPress}
          onLongPress={this._onLonePress}>
          <View style={styles.button}>
          </View>
        </TouchableHighlight>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  button:{
    width: 200,
    height: 200,
    borderRadius: 100,
    backgroundColor: 'red'
  },
  touchable: {
    borderRadius: 100
  }
});

AppRegistry.registerComponent('gesture', () => gesture);

在上面的代码中,主要部分就是一个作为容器的View,一个作为按钮的View,因为想要给这个按钮绑定Touch手势,因此使用了TouchableHighlight这个和Touch相关的组件,将它作为按钮的一个包裹,在这个包裹的props中规定相应的回调即可。

前面提到了和Touch相关的组件一共有四个,它们的基本用法都很类似,只是实现的功能不太相同。先说最常用的TouchableHighlight,这个组件的作用,除了给内部元素增加绑定事件之外,还负责给内部元素增加“点击态”。所谓的“点击态”,就是在用户在点击的时候,会产生一个短暂出现覆盖层,用来告诉用户这个区块被点击到了。TouchableNativeFeedback这个组件只能用在安卓上,它可以针对点击在点击区域中显示不同的效果,例如最新安卓系统中的Material Design的点击波纹效果。TouchableOpacity这个组件用来给为内部元素在点击时添加透明度。TouchableWithoutFeedback这个组件只响应touch手势,不增加点击态,不推荐使用。

四个组件的用法大致相同,具体的细节方法可以参看具体文档。回头看上面的代码,在iOS模拟器中运行的效果图如下:

2015-09-16 1 04 19

在上面的代码中,TouchableHighlight组件上绑定了4个方法:

  • onPress
  • onPressIn
  • onPressOut
  • onLonePress

这4个方法也是React-Native帮助用户实现的4个手势,通过在4个相应的回调函数中输出不同的内容,我们可以研究4个手势出现的条件和顺序。打开chrome debug模式,点击模拟器中的按钮,可以看到浏览器控制台里面的输出内容:

rn

原生应用之所以为原生,和web应用相比,有两个比较主要的区别:

  1. 原生应用会对触摸事件作出响应,也就是“点击态”;
  2. 原生应用可以选择中途撤销触摸事件;

前面一点比较清楚,第二点选择中途撤销是什么意思呢?举个最简单的例子,用微信聊天的时候,点击了一个好友,可以进入聊天界面,但是如果我点中了一个好友,突然又不想和他聊天了,我会多按一会,然后将手指划开,这样就可以撤销刚才的触摸事件,就好像根本就没有点击过一样。平时使用得太习惯,可能没有意识到原来这个操作是撤消了触摸事件,现在回过头一想,还真是这么一回事。

通过前面的实验,我们可以对press,pressIn,pressOut,longPress事件的触发条件和触发顺序有一个比较清晰的了解:

  1. 快速点击,只会触发press事件
  2. 只要在点击时有一个“按”的操作,就是比快速点击要按的久一点,就会触发pressin事件
  3. 如果同时绑定了pressIn, pressOut和press事件,那么当pressIn事件触发之后,如果用户的手指在绑定的组件中释放,那么接着会连续触发pressOut和press事件,此时的顺序是pressIn -> pressOut -> press。而如果用户的手指滑到了绑定组件之外才释放,那么此时将会不触发press事件,只会触发pressOut事件,此时的顺序是pressIn -> pressOut。后一种情况就是前面所说的中途取消,如果我们将回调函数绑定给press事件,那么后一种情况中回调函数并不会被触发,相当于"被取消"。
  4. 如果绑定了longPress事件,那么在pressIn事件被触发之后,press事件不会被触发,通过打点计时,可以发现longPress事件的触发时间大概是在pressIn事件发生383ms之后,当longPress事件触发之后,无论用户的手指在哪里释放,都会接着触发pressOut事件,此时的触发顺序是 pressIn -> longPress -> pressOut

以上内容就是对React-Native对Touch事件的实现和用法分析,对于大部分应用来说,使用这四个Touch*组件再配合4个press事件就能对用户的手势进行响应。但是对于比较复杂的交互,还是得使用React-Native中的gesture responder system。

gesture responder system

在React Native中,响应手势的基本单位是responder,具体来说,就是最常见的View组件。任何的View组件,都是潜在的responder,如果某个View组件没有响应手势操作,那是因为它还没有被“开发”。

将一个普通的View组件开发成为一个能响应手势操作的responder,非常简单,只需要按照React Native的gesture responder system的规范,在props上设置几个方法即可。具体如下:

  • View.props.onStartShouldSetResponder
  • View.props.onMoveShouldSetResponder
  • View.props.onResponderGrant
  • View.props.onResponderReject
  • View.props.onResponderMove
  • View.props.onResponderRelease
  • View.props.onResponderTerminationRequest
  • View.props.onResponderTerminate

乍看之下,这几个方法名字又长有奇怪,但是当了解了React Native对手势响应的流程之后,记忆这几个方法也非常容易。

要理解React Native的手势操作过程,首先要记住一点:

一个React Native应用中只能存在一个responder

正因为如此,gesture responder system中才存在_reject和_terminate方法。React Native事件响应的基本步骤如下:

  1. 用户通过触摸或者滑动来“激活”某个responder,这个步骤由View.props.onStartShouldSetResponder以及View.props.onMoveShouldSetResponder这两个方法负负责处理,如果返回值为true,则表示这个View能够响应触摸或者滑动手势被激活
  2. 如果组件被激活,View.props.onResponderGrant方法被调用,一般来说,这个时候需要去改变组建的底色或者透明度,来表示组件已经被激活
  3. 接下来,用户开始滑动手指,此时View.props.onResponderMove方法被调用
  4. 当用户的手指离开屏幕之后,View.props.onResponderRelease方法被调用,此时组件恢复被触摸之前的样式,例如底色和透明度恢复之前的样式,完成一次手势操作

综上所述,一次正常的手势操作的流程如下所示:

响应touch或者move手势 -> grant(被激活) -> move -> release(结束事件)

来段简单的示例代码:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
} = React;

var pan = React.createClass({
  getInitialState(){
      return {
        bg: 'white'
      }
  },
  componentWillMount(){
    this._gestureHandlers = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg: 'red'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg: 'white'})}
    }
  },
  render: function() {
    return (
      <View style={styles.container}>
        <View
          {...this._gestureHandlers}
          style={[styles.rect,{
            "backgroundColor": this.state.bg
          }]}></View>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rect: {
    width: 200,
    height: 200,
    borderWidth: 1,
    borderColor: 'black'
  }
});

AppRegistry.registerComponent('pan', () => pan);

运行这段代码,当中间的正方形被激活时,底色变为红色,release之后,底色又变为白色。

rn1

上面是正常事件响应流程,但是当应用中存在不止一个手势responder的时候,事情可能就复杂起来了。比如应用中存在两个responder,当使用一个手指激活一个responder之后,又去激活另一个responder会怎么样?因为React Native应用中只存在一个Responder,此时就会出现responder互斥的情况。具体来说过程如下:

  1. 一个responder已经被激活
  2. 第一个responder还没有被release,用户去尝试去激活第另一个responder
  3. 后面将要被激活的responder去和前面还没有被释放的responder“协商”:兄弟,你都被激活这么久了,让我也活动一下呗?结果两种情况:
    • 前面的responder比较“强硬”,非要占据唯一的responder的位置
    • 前面的responder比较“好说话”,主动release
  4. 前面一种情况,后面的responder的onResponderReject方法被调用,后面的responder没有被激活
  5. 后面一种情况,后面的responder被激活,onResponderGrant方法被调用 ,前面的responder的onResponderTerminate方法被调用,前面的responder的状态被释放

上面的步骤中,比较重要的部分是第三步“协商”,这个步骤由onResponderTerminationRequest这个方法的返回值决定,如果一个responder的这个方法的返回值是true,那么说明这个responder是“好说话”的方法,反之则是“强硬”的方法。

由于在iOS simulator上不好模拟这个过程,大家可以自行编写应用在真机上对这个“协商”的步骤进行尝试。

和web的事件的冒泡过程类似,React Native中的事件遵循的也是冒泡机制。默认情况下,当潜在的responder的互相嵌套时,最顶部的responder将会响应事件。大部分时候,这也是开发者想要的逻辑。但是我们可以来自定义响应事件的responder。具体来说,通过:

  • View.props.onStartShouldSetResponderCapture
  • View.props.onMoveShouldSetResponderCapture

两个方法来进行设置。当某个潜在responder的这两个方法的其中一个返回值为true时,即使当前的View组件不在最顶部,唯一一个responder的位置也会由它占据。看下面的例子:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
} = React;

var pan = React.createClass({
  getInitialState(){
      return {
        bg: 'white',
        bg2: 'white'
      }
  },
  componentWillMount(){
    this._gestureHandlers = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg: 'red'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg: 'white'})},
    }
    this._gestureHandlers2 = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg2: 'green'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg2: 'white'})}
    }
  },
  render: function() {
    return (
      <View style={styles.container}>
        <View
          {...this._gestureHandlers}
          style={[styles.rect,{
            "backgroundColor": this.state.bg
          }]}>
            <View
              {...this._gestureHandlers2}
              style={[styles.rect2,{
                "backgroundColor": this.state.bg2
              }]}
            >

            </View>
          </View>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rect: {
    width: 200,
    height: 200,
    borderWidth: 1,
    borderColor: 'black',
    justifyContent: 'center',
    alignItems: 'center',
  },
  rect2: {
    width: 100,
    height: 100,
    borderWidth: 1,
    borderColor: 'black'
  }
});

AppRegistry.registerComponent('pan', () => pan);

这是正常的情况,当用户触摸最顶部的正方形时,最顶部的正方形会响应触摸事件,底色变为绿色,外层的正方形则不会响应触摸事件:

rn2

而当在外层的View中加入

      onStartShouldSetResponderCapture: () => true,
      onMoveShouldSetResponderCapture: ()=> true,

两个方法之后,即使点击最顶部的小正方形,响应的responder也变为了外层的正方形:

rn3

和web开发中的事件参数类似,以上的每个方法都有一个evt参数,在事件发生的过程中,这个evt参数的nativeEvent属性的各个值能够标示手势进行的状态,如下所示:

2015-09-16 11 23 24

参数的具体含义可以参看React Native文档。

PanResponder

除了gesture responder system之外,React Native还抽象出了一套PanResponder方法,和gesture responder system相比,PanResponder方法的抽象程度更高,使用起来也更为方便。在使用PanResponder的时候,相应手势的逻辑和流程都不变,只需要根据文档对几个方法名称作修改即可。PanResponder的好处是:对于每个方法,除了第一个evt参数之外,开发者还可以使用第二个参数gestureState,这个gestureState是一个对象,包含手势进行过程中更多的信息,其中比较常用的几个是:

  • dx/dy:手势进行到现在的横向/纵向相对位移
  • vx/vy:此刻的横向/纵向速度
  • numberActiveTouches:responder上的触摸的个数

通过使用PanResponder,我们可以非常方便的实现drag & drop的效果。代码如下所示:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  PanResponder
} = React;

var pan = React.createClass({
  getInitialState(){
      return {
        bg: 'white',
        top: 0,
        left: 0
      }
  },
  componentWillMount(){
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: ()=> true,
      onPanResponderGrant: ()=>{
        this._top = this.state.top
        this._left = this.state.left
        this.setState({bg: 'red'})
      },
      onPanResponderMove: (evt,gs)=>{
        console.log(gs.dx+' '+gs.dy)
        this.setState({
          top: this._top+gs.dy,
          left: this._left+gs.dx
        })
      },
      onPanResponderRelease: (evt,gs)=>{
        this.setState({
          bg: 'white',
          top: this._top+gs.dy,
          left: this._left+gs.dx
      })}
    })
  },
  render: function() {
    return (
      <View style={styles.container}>
        <View
          {...this._panResponder.panHandlers}
          style={[styles.rect,{
            "backgroundColor": this.state.bg,
            "top": this.state.top,
            "left": this.state.left
          }]}></View>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rect: {
    width: 200,
    height: 200,
    borderWidth: 1,
    borderColor: 'black',
    position: 'absolute',
  }
});

AppRegistry.registerComponent('pan', () => pan);

rn4

总结

以上的内容就是我近一段事件来对React Native手势的学习和理解。讲了一些基本原理,但是要实现一些更加复杂的手势,例如pinch、rotate、zoom,还需要更进一步的研究和学习。

===============

使用CSS线性渐变生成彩虹图

之前在美国同性婚姻在全美合法之后,社交网络上很多人为了表达自己对同性婚姻的支持,纷纷将自己的头像换成了彩虹图。如下图所示:

很多生成彩虹图的JS库也应运而生,比如说国外的有prideify.js。predeify这个库的基本原理是把图片画到canvas上,生成彩虹图以后再使用canvas的toDataURL方法将图片转换为base64格式,再替换原有图片的src。

线性渐变并不是一个新的css属性,但是在过去的项目中并没有使用过它。在今天团队的一次内部分享会上有同学介绍了一些线性渐变的使用技巧,我觉得这个属性很实用,就用这个属性做了一个彩虹图。原理和写法都非常简单。

首先简单回顾一下线性渐变的语法:

background: -webkit-linear-gradient(start_point,from_color,to_color)

start_point的取值可以是角度,比如30deg,也可以是预设值top、bottom、left、right,或者预设值两两组合比如top left。而from_color则是渐变色起点,to_color是渐变色终点。比如我们这么写:

background: -webkit-linear-gradient(top, red, green);

这样元素背景将会自上而下颜色由red线性渐变为green。

渐变的颜色可以多个,并在可以指定颜色停顿的位置,所以可以这么写:

background: -webkit-linear-gradient(top, red , yellow 50%, green);

这样元素的背景将会从上到下由red线性渐变,在50%的地方变为yellow,继续线性渐变为green。

如果我们在制定停顿的地方做一点变化:

background: -webkit-linear-gradient(top, red  50%, green 50%);

这样元素的背景将会在50%的地方划分为上下两个部分,上面为red,下面为green,从视觉上来看没有发生渐变。

了解了上面这么多,我们就可以来用线性渐变生成彩虹图了。元素为一个div

  <div class="rainbow"></div>

接下来用背景图将图片加载进div中:

           .rainbow{
                width: 300px;
                height: 300px;
                position: relative;
                background-image: url(./dog.jpg);
                background-size: 100% 100%;
            }

接下来使用伪类::after创建彩虹mask:

        .rainbow::after{
            content: "";
            display: block;
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0px;
            left: 0px;
            background: -webkit-linear-gradient(top, #ff3e18 16.66%,#fc9a00 16.66%, #fc9a00 33.32%, #ffd800 33.32%, #ffd800 49.98%, #39ea7c 49.98%, #39ea7c 66.64%, #0bb2ff 66.64%, #0bb2ff 80.3%, #985aff 80.3% );
            opacity: 0.5;
        }

代码关键的部分在于background中的linear-gradient属性,我们在这里将元素高度从上到下分为6份,并在对应的点设置渐变停顿色,最后将伪类透明度设置为0.5,这样就能生成一张彩虹图了。

css的渐变功能非常强大,远远不止本文中提到的这些。更多酷炫的css渐变例子可以查看这里

css真的很强大,大家一起来学好它。love wins。

2016-02-24 5 52 55

Rill.js文档

Rill

安装

Rill支持所有最新版本的nodejs,以及所有现代浏览器,包括ie10。
你可以使用你喜欢的版本管理器安装一个支持的 node/iojs版本。

  $ nvm install stable
  $ npm i rill
  $ node my-rill-app.js

Application

一个Rill应用是一个包含一个中间件数组的对象,这些中间件能够在请求到来时以一种类栈的方式组合并执行。Rill和其他的nodejs框架例如Express,Hapi以及Koa类似,但是它们之间的一个重要区别是:Rill的代码能在浏览器里面运行。

Rill本身包含许多重要的用于构建现代web应用的功能。包括:路由、重定向、cookie等等。一个典型的全局渲染方案是可以使用 @rill/react 来让单页react应用能够无缝的在server端和浏览器端同时运行。

  // 创建一个应用()
  const Rill = require('rill');
  const app = new Rill();

app.listen({port,host,backlog,tls},callback)

一个Rill应用并不是“1对1”的对应一个HTTP服务器。一个或者更多的Rill应用可以通过挂载来组成大型应用,你甚至可以使用一个HTTP服务器来监听多个端口。

如果提供了 tls 参数 listen 方法将会启动一个HTTPS服务器。这个tls参数和node中 TLS模块用法一致。

ipportbacklog 参数是可选参数,并且会在服务端的 listen方法中被使用。而在浏览器端这些参数都会被忽略掉。

下面的例子是一个简单的绑定到3000端口的服务器:

  app.listen({ port: 3000 });

或者我们可以使用同样的API开启一个HTTPS服务器:

  var options = {
  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
  };

  app.listen({ port: 8000, tls: options });

app.handler()

返回一个适用于http.createServer()方法的回调函数来处理一个请求。你也可以使用这个回调函数将你的Rill应用挂载到一个Connect/Express应用中。

这意味着你可以在HTTP和HTTPS协议下或者多个地址下运行:

// 对于同构应用你可以使用 @rill/http(s)模块而不是node的http模块
http.createServer(app.handler()).listen(3000);
http.createServer(app.handler()).listen(3001);

app.stack()

以数组的形式返回当前应用的中间件栈。

app.setup(function...)

中间件的简单语法糖,同时做的不仅仅是将其添加到中间件栈中。这对于修改/扩展Rill或者其他复杂的插件很有用。

  app.setup((myapp)=> {
    myapp === app; // true
    myapp.customMethod = ...;
    myapp.use(customMiddleware);
  });

app.use(function|application...)

为当前应用添加指定中间件。查看 [中间件]部分获取更多相关信息。在Rill中你甚至可以挂载其他应用。

  // 简单的日志中间件
  app.use(async ({ req, res }, next)=> {
  const start = new Date;

  // Rill 使用promise进行流程控制
  // ES2016 async 函数也可以使用!
  await next();

  const ms = new Date - start;
  console.log(`${req.method} ${req.url} - ${ms}`);
  });

app.at(function|application...)

只有当路径匹配时中间件才会运行。Rill和Express和其他node框架一样使用同样的路径匹配库。你可以在这里测试你的路径正则表达式。

  // 匹配路由请求
  app.at("/", ...);

  // 路由参数将会放在req.parmas变量中
  // 例如使用 /api/user
  app.at("/api/:resource", ({ req })=>
  req.params.resource === "user";
  );

app|METHOD|([path],function|application...)

添加只有在可选路和请求方法匹配上的时候才会运行的中间件。

  // 匹配所有请求
  app.get(...);

  // 在使用GET方法请求时匹配路径
  app.get("/", ...);

  // 用一种更模块化的方式挂载路由
  app.get("/api/*",
    rill().get("/v1", ...) // 匹配 `/api/v1`
  )

app.host(hostname,function|application...)

添加一个只有在hostname匹配时会运行的中间件。hostname的正则匹配方法和其他的路由方法的匹配方法相同。你可以在这里测试你的hostname正则表达式。

  // 匹配特定域名
  app.host("test.com", ...);

  // 可以使用req.subdomains来匹配子域名
  // 例如使用 api.user.test.com
  app.host("api.:resource.test.com", { req })=>
  res.subdomains.resource === "user";
  res.subdomains == ["api", "user"];
  );

错误处理

Rill以中间件中promise的形式处理错误。在Rill中next中间件总是会返回一个promise。

  app.use(function (ctx, next) {
    // 在栈的最后捕获错误。
    return next().catch(function (err) {
      log.error('server error', err);
    });
  });

redux中间件实战

tl;dr

2015年,随着react框架在前端开发领域的持续火热,单数据流向也开始逐渐被前端开发人员所认识和使用。单向数据流向指的是在整体web应用中,数据都是单向流动的。回想一下我们的校园时光,每当你没有生活费的时候,你会打电话找妈要钱,然后家里人去银行给你汇款,然后你去银行把钱取出来。在这个过程中,钱的流向是单向的:你 => ** => 银行 => 你。这样的流程可以确保你不会去银行随意取钱挥霍,而且流程非常清晰。

同样的道理,web应用中数据的单向流动也是很有意义的。回想一下以前我们曾经干过的事情,我们是不是经常把状态存在视图中。比如下面的代码:

$(".button").click(function(){
  if(this.hasClass("highlight")){
    this.removeClass("highlight");
  }else{
    this.addClass("highlight");
  }
})

除了把状态放在class里面,dom的dataset也是我们经常存放应用状态的地方。这样把状态和视图的耦合,对于编写读可维护的代码非常不利,尤其是在我们编写大型应用的时候。

Facebook的工程师提出了flux数据架构,它是数据单向流动的一种实现方式,目前实现的库也很多,比如reflux和Fluxxor。redux库灵感来源于flux,但和flux又有以下几点不同:

  1. redux不存在一个中心dispatcher
  2. redux中只有唯一一个store

redux代码简单轻巧,而且还有一个非常重要的特性:中间件。

redux中间件实战

本文假设你已经对redux有一些了解,如果你是一个redux新手,请先看redux中文文档

redux代码中使用了很多函数式编程技巧,redux中间件也是一个高阶函数。我们用最简单的redux-thunk为例子:

function thunkMiddleware({ dispatch, getState }) {
  return next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }
    return next(action);
  };
}

首先我们需要记住的第一点是:

redux中间件是有格式的,也就是所谓的signature

({dispatch, store}) => (next) => (action) => {}

假设我们现在编写了两个最简单的中间件:

import {createStore, applyMiddleware, combineReducers} from "redux"

let reducer = (store={},action)=>{
    return store
}

let logger1 = ({dispatch, getState}) => (next) => (action) => {
    console.log("第一个logger开始");
    next(action);
}

let logger2 = ({dispatch, getState}) => (next) => (action) => {
    console.log("第二个logger开始");
    next(action);
}

let store = createStore(reducer,applyMiddleware(logger1,logger2));

store.dispatch({
  type: "type1",
})

运行代码结果,我们可以看到控制台输出了:

第一个logger开始
第二个logger开始

我们要记住的第二点:

中间件是对初始dispatch方法的包装

在上面的例子中,我们使用applyMiddleware对初始的dispatch方法进行了包装,因此我们在调用dispatch方法的时候,控制台有相应的输出。

我们要记住的第三点:

包装后的中间件从左往右进行包装,并从左往右执行

redux源码中使用了compose函数对中间件进行包装,使用applyMiddleware包装dispatch以后,新的dispatch方法可以可以理解为下面的方法:

  let new_dispatch = (...args) = >  logger1(logger2(dispatch(...args)))

我们要记住的第四点:

在中间件中调用next方法,控制权到达下一个中间件,调用dispatch方法,控制权从头开始

假设把logger2修改为下面的代码:

let logger2 = ({dispatch, getState}) => (next) => (action) => {
    console.log("第一个logger开始");
    dispatch(action);
}

那么控制台将持续输出"第一个logger开始 第二个logger开始",并最终程序爆栈终止。这是因为调用dispatch使控制权反复回到了第一个中间件。而next的调用方法,作用和express中间件的next相同。

我们要记住的最后一点:

中间件的return只会被上一级捕获。

let logger1 = ({dispatch, getState}) => (next) => (action) => {
    console.log("第一个logger开始");
    console.log(next(action));
        return 123;
}

let logger2 = ({dispatch, getState}) => (next) => (action) => {
    console.log("第二个logger开始");
    next(action);
        return 456;
}

let store = createStore(reducer,applyMiddleware(logger1,logger2));

console.log(store.dispatch({
  type: "type1",
}));

logger1中的return能被最外层的console.log捕获,而logger2的return只会被logger1捕获。

结论

开发redux中间件只需要记住上面五点,一起来玩redux吧。

CSS3 :checked伪类选择器妙用

CSS3中的伪类选择器例如:hover,:before,:after在已经在前端开发中被广泛使用,但是可能开发者还不太熟悉:checked这个伪类选择器。本文将介绍:checked这个伪类选择器以及如何使用它写出更加有优雅的代码。

:checked伪类选择器顾名思义,表示的是type="checkbox"的input元素被选中的状态。在前端开发中,我们常用:hover伪类来设置鼠标悬浮时的样式,而由于checked状态的改变需要用户进行点击操作,使用:checked伪类,我们则可以设置鼠标点击后的状态。在使用zepto、jQuery库时,有一个经常使用的方法toggle用来隐藏和再现页面上的某个元素,了解:checked伪类的定义之后,我们完全可以用纯CSS实现toggle效果。

首先定义页面结构:

  <style>
     .toggle-item{
         width: 100px;
         height:  100px;
         background-color: pink;
     }
   </style>
   <div class="toggle">
       <input id="toggle-trigger" type="checkbox" />
      <div class="toggle-item"></div>
  </div>

接着,我们对#toggle-trigger的选中态进行设置

    #toggle-trigger:not(checked) ~  .toggle-item{
       display: block;
     }
   #toggle-trigger:checked  ~  .toggle-item{
       display: none;
     }

此时,我们通过点击选中或者取消选中checkbox,就能对.toggle-item进行隐藏和再现。

2016-06-01 2 13 14
2016-06-01 2 16 37

但是我们想跟进一步,把toggle-trigger的范围扩展到checkbox之外,因为在展现toggle效果时,触发toggle的部分并不局限于checkbox一种形式。这时我们只需要使用label标签即可,label标签有一个for属性,通过设置for属性,可以将label标签指向特定的input元素,同时将checkbox隐藏,既可以达到点击label标签来触发toggle的效果。

<style>
 .toggle-item{
     width: 100px;
     height:  100px;
     background-color: pink;
 }
 #toggle-trigger {
    display: none;
 }
 #toggle-trigger ~ :not(checked) ~  .toggle-item{
    display: block;
 }
 #toggle-trigger:checked  ~  .toggle-item{
   display: none;
 }
</style>
    <div class="toggle">
       <label for="toggle-trigger">触发器</label>
       <input id="toggle-trigger" type="checkbox" />
      <div class="toggle-item"></div>
    </div>

2016-06-01 2 36 45

根据caniuse的数据,:checked伪类的支持程度基本达到了100%。在下一个项目里面,你会愿意使用它嘛?

参考文章

You Don't Need JavaScript for That!

我理解的FRP

文章正在不断完善中,请回头再来看

tl,dr

写作本文的原因有3:

  • 工作中需要进行一个关于响应式编程、函数式编程的一个调研,其中一项产出是调研报告
  • 我个人对函数式编程和响应式编程有好奇,虽然使用过underscore.js这样的函数式JS以及React这样的响应式JS框架,但是一直是知其然却不知其所以然,遂决心借此机会弄个明白
  • 对近一段时间的学习做个记录和总结

本文(可能)会涉及到的内容:

  • Functional Javascript 编程
  • Reactive Javascript 编程
  • Stream in JavaScript
  • Rx.js

Functional JavaScript

提到JS函数式编程,大多数人第一事件都会想到map、reduce这样的方法。确实,map和reduce是函数式编程中两个非常重要的方法,但是JS函数式编程却远远不止是map和reduce。

createjs即学即用

canvas是HTML5中一个非常重要的元素,使用canvas可以让开发者制作出很多非常酷炫的的动画效果。

同构JavaScript,其实可以更简单

本文为译文,原文地址为:https://medium.com/@pierceydylan/isomorphic-javascript-it-just-has-to-work-b9da5b0c8035#.9a0873elf

isomorphic ,发音类似为(埃搜摸非克)

意为拥有类似的结构或外表,但源头却不同

1-uac5pb_amuak73m73j2htq

自从NodeJS诞生以来,人们就开始爱上了你可以在不同代码栈之间“共享”JavaScript经验的想法。这样的想法(在理论上)拥有诸多好处,包括可以减少不同语言之前的上下文切换,前端和后端开发能更容易的过渡,以及最有希望的好处是获得共享代码的能力。

好吧,我确实已经在browserifywebpack的帮助下使用了同样的效用函数...确定我可以在浏览器里面使用同样的api进行AJAX操作嘛?没问题!移动表格和文件也可以嘛?没问题!那如果我需要探测环境呢?我并没有使用meteor!也没问题!甚至DOM现在也可以通过ReactDeku和其他很多库实现的“虚拟DOM”实现同构。你也可以不适用虚拟DOM,而采用其他的工具例如morphdomset-domdiffHTML来编写简单的“静态”html代码。

const _ = require("lodash")

_.chain(emails)
    .chunk(100)
    .filter("to")
    .unshift(Promise.resolve())
    .reduce((p, batch)=> {
        p.then(()=> {
            fetch("http://myapi.com/email", {
                method: "POST",
                body: JSON.stringify(batch)
            });
        });
    })
    .value()
    .then(()=> {
        console.log("Emails sent!");
    });

上面的代码看起来确实很酷。现在我可以使用同样的效用函数,ajax和模板在Node和浏览器端编写一个应用。但是这对于一个应用来说还远远不够。我们还需要其他很多东西!我需要路由,重定向,session,cookie和其他更多东西来制造一个应用!在Node中我能使用中间件来组织整个应用的代码,我可以将特征相互隔离开,并方便的进行测试!这样的做法可以简单的、自上而下的、插件式的去构建html代码。那为什么我们不能用同样的方式创建DOM呢?React在这个方面更进了一步,但是它做的还不够。

各位女士们乡亲们,我最近的工作一直致力于让编写同构应用更加简单愉快。在客户端和服务器之间编写更少的模板和更少的上下文切换。渐进增强,更快的加载速度,SEO以及用一种更好的方法去思考web应用。现在有一种方法可以让你的浏览器和Node之间共享90%以上的代码。它是一个已经开发了一年以上的框架,在这里我要先感谢我在Tiller Digital工作的同事,以及千千万万和帮助开发同构工具的朋友们。这个框架的名字叫做:Rill

呵呵,又一个框架。它的好处都有啥,谁能说出来就送给他。

Rill在我们经常使用的Node功能,例如HTTP服务器,上进行了抽象。然他它使用同构的方式暴露出http api,这要感谢 @rill/http。路由,重定向,cookie,session,刷新,错误处理,甚至通用的头信息例如“user-agent”,“referrer”和“local”均使用同构的方式进行了抽象。你最终可以在浏览器端和服务器端使用同样的api获取一个用户的所有数据,最终所有的代码都能以Node的方式编写。

Rill不仅仅是上面说的这些,@rill/http仅仅只是其中一个模块。Rill主要的库更贴近Express或者Koa,三者的api也类似。它能够以同构的方式使用中间件,并为任何应用提供一个最终模块。它提供了一种数据流动的方式。具体信息你可以查看文档

// 用类似的方式创建一个app

const app = require("rill")();

// 使用一些中间件吧!

app.use(require("@rill/logger")()); // 设置日志

app.use(require("@rill/react")()); // 设置react render

// 我们来创建主页

app.get("/", ({ req, res })=> {

  // 让response body设置为一个React组件

  // 这会在浏览器端渲染同步html,并在浏览器端更新dom

  res.body = <MyApp title="Rill"/>;

});

// Listen将开启一个node服务器,或者在浏览器内开启事件监听器.

app.listen({ port: 3000, ip: "0.0.0.0" });

// 非常简单的单页React组件

function MyApp (props) {

  // 使用 JSX

  return (

    <html>

        <head>

            <title>My App - Home</title>

        </head>

        <body>

            Welcome to { props.title } life.

            <a href="/away">Take me away!</a>

            <script src="/app.js"/>

        </body>

    </html>

  );

}

就是这样,我们的主页可以完全在浏览器内渲染,当然也可以在node服务器内渲染。但是这并不算什么,导航该怎么办?“/away”又是什么?

// 我们来注册另一个路由

app.get("/away", ({ req, res })=> {

  res.body = <AwayPage/>;

});

// 我们来创建另一个简单的React组件

function AwayPage (props) {

  return (

    <html>

        <head>

            <title>My App - Away</title>

        </head>

        <body>

            Well... Theres not much here.

            <a href="/">Take me back!</a>

            <script src="/app.js"/>

        </body>

    </html>

  );

}

现在你可以向前向后进行导航了,浏览器也能正确的处理它。即使在浏览器内关闭JavaScript,你的服务器也能正确的进行处理!在环境切换的时候再也不会憋手蹩脚了,再也没有“服务器代码”或者“浏览器代码”了,你仅仅需要渲染你的视图。

这简直太酷了,但是如果我的应用只是静态内容,那使用Rill岂不是有点杀鸡用牛刀了(尽管也是有一些好处的)。我的CRUD应用呢?如何操控用户数据呢?其实这可以简化为另一个问题“如果没有前端JavaScript代码应该怎么进行CRUD操作呢?”。Rill可以让你的浏览器假装自己获得node服务器的能力,它甚至可以处理表单提交!让我们来增加一个表单!

// 使用一个同构form-data/body解析器

app.use(require("@rill/body")());

// 正常的注册我们的表单页面

app.get("/my-form-page", ({ req, res })=> {

 res.body = <MyForm/>

});

// 设置我们的post路由

app.post("/my-form-submit", ({ req, res })=> {

  // Analyze the response body (works in node and the browser)

  // 分析response body (在浏览器端和node端同样适用)

  req.body; //-> { email: ... }

  req.files; // 表单提交的文件

  // Perform the business logic (typically calling some api)

  // 执行业务逻辑(一般来说会调用一些api)

  ...

  // Finally take the user somewhere meaningful.

  // 最终将用户导航到某个地方

  res.redirect("/thank-you");

});

function MyForm (props) {

  return (

      <html>

          <head>

              <title>My App - Form</title>

          </head>

          <body>

              Please, your data is important for my business!                    

              <form action="/my-form-submit" method="POST">

                  Email: <input name="email">

                  <button type="submit">Subscribe</button>

              </form>

              <a href="/">Take me back!</a>

              <script src="/app.js"/>

          </body>

      </html>

  );

}

这简直太棒了。我们可以像使用Node一样进行编程,而且渐进增强也很容易。你需要一个在输入的时候就能进行搜索的input框吗?你也可以简单地使用React去触发提交按钮的“onClick”事件。它这样就能运行,而且和手动触发一样快。除此之外,如果浏览器端的JavaScript无法运行,你的服务器也能直达如何正确的“反应”。

这就是Rill。你可以在其中发现你曾经熟悉的“只在服务器端运行”方法的表达性和简单性,同时也可以尽可能的摆脱大部分前端逻辑。它带来了很多新东西,它也绝对是一种构建前端应用不同的方式,但对于你来说也不是完全的陌生。你完全可以编写你喜爱的Node代码,然后让它在浏览器端也运行起来。视图部分可以在服务端进行快速初始渲染,同时在浏览器端进行快速的再次渲染。它是浏览器和服务器世界中最好的部分,它是JavaScript Web开发的顶点,它简直就是圣杯。

如果你有关于Rill,Isomorphic/Universal Javascript,或者如何使用Rill创建真实应用的问题,请来这个gitter page提问。

前端是什么鬼系列 -- MessageChannel是什么鬼

主题:介绍MessageChannel API及其用法

预计阅读时间:2min

看某个JS库源码的时候发现了MessageChannel,这是什么鬼?

MessageChannel接口是信道通信API的一个接口,它允许我们创建一个新的信道并通过信道的两个MessagePort属性来传递数据

简单来说,MessageChannel创建了一个通信的管道,这个管道有两个口子,每个口子都可以通过postMessage发送数据,而一个口子只要绑定了onmessage回调方法,就可以接收从另一个口子传过来的数据。

一个简单的例子:

   var ch = new MessageChannel();
   var p1 = ch.port1;
   var p2 = ch.port2;
   p1.onmessage = function(e){console.log("port1 receive " + e.data)}
   p2.onmessage = function(e){console.log("port2 receive " + e.data)}
   p1.postMessage("你好世界");
   p2.postMessage("世界你好")

MessageChannel用法很简单,但是功能却不可小觑。例如当我们使用多个web worker并想要在两个web worker之间实现通信的时候,MessageChannel就可以派上用场:

main.html

  <script>
        var w1 = new Worker("worker1.js");
        var w2 = new Worker("worker2.js");
        var ch = new MessageChannel();
        w1.postMessage("initial port",[ch.port1]);
        w2.postMessage("initial port",[ch.port2]);
        w2.onmessage = function(e){
            console.log(e.data);
        }
  <script>

worker1.js

  var port;
   onmessage = function(e){
    if(e.data == "initial port"){
        port = e.ports[0];
    }else{
        setTimeout(function(){
            port.postMessage("this is from worker1")
        },2000)
      }
  }

worker2.js

    var port;
    onmessage = function(e){
    if(e.data == "initial port"){
        port = e.ports[0];
        port.onmessage = function(e){
            postMessage(e.data)
        }
       }
   }

在上面这个例子中,首先通过web worker的postMessage方法把两个MessageChannel的port传递给两个web woker,然后就可以通过每个port的postMessage方法传递数据了。由于在worker中无法通过console.log打出传递的数据,因此我们通过给w2绑定onmessage回调函数来验证传递是否成功。最终我们可以看到控制台中输出

this is from worker1

而传递的路径为:

w1=> ch1 => ch2 => w2

根据canisue的数据显示,目前大多数浏览器都对MessageChannel有很好的支持,所以不妨在下一个项目里面使用一下这个API吧!

学点无用技能系列 -- 开篇

前端好无聊,还是玩点新东西吧。就好像我很喜欢的一部电影《 Planet Terror 》里面的护士通过无用技能 -- 注射器射僵尸 -- 打败僵尸成功活命一样,说不定现在学的无用技能以后就有用了呢。

希望这个系列不会烂尾!嗯!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.