402's Dojo

Javascript for React Native语法教程 - GUIDE|Styles

对非前端开发者学习React Native来说,可能是最好的JS语法教程。
作为一个iOS工程师,当我第一眼看到React代码的时候,脑海里浮现的到处都是WTF这货是啥?这里为啥有分号or等号,这里为啥没有?这个大括号是做啥的?这是在定义一个类?or What ever?。由于负责工作内容的原因,JS代码我也经常见,但是读一种语言和编写它完全是两码事,你必须知晓当前编写范例中的全部语法才能真正掌握它,尤其想Javascript这样的语言,而像我这么懒惰的人当然是希望用到某种JS语法的时候再去学习,而不是先花半个月时间啃一本Javascript的语法(我在其他语言上试过但是效果并不好)。于是便有了这篇Javascript的语法教程,专门给开始学习React Native的非前端开发者准备。
谁适合看这篇教程:

  1. 至少接触过一门静态 or 动态编程语言;
  2. 正在或者打算开始学习React Native。
    讲解顺序按照React Native教程的顺序,解释每一个代码实例涉及到的JS语法知识,尽量不去涉及代码理解过程中用不到的语法知识。如果在阅读过程中遇到没有涉及到同时又不明白的语法请告诉我,我会及时更新这系列的文章。
    由于作者也是初学者,文章内容在不断的矫正中,不求严格的正确,但求以最快的速度理解学习Javascript in React Native,不断修正自己的认识的同时使用它。
    对应React Native章节:
    Guides - Style

第一段代码 - Declare Styles

var styles = StyleSheet.create({
base: {
width: 38,
height: 38,
},
background: {
backgroundColor: '#222222',
},
active: {
borderWidth: 2,
borderColor: '#00ff00',
},
});

声明变量

JS使用var关键字声明一个变量,无论什么时候需要一个变量,尽情的var吧。

类和对象

对象

和其他JS教程不同,让我们首先来了解Javascript的对象。学习的目的是编程,只有掌握JS对象才能开始写第一个真正的JS程序,而传统教程中走到这一步之前你需要学习一大坨(非常大的一坨)语法知识却不能开始写一段程序?(别跟我说hello world。)
JavaScript 中的所有事物都是对象。对象只是一种特殊的数据。对象拥有属性方法
访问对象属性的语法:objectName.propertyName
调用对象方法的语法:objectName.methodName()
因此上面代码中的第一行的含义是:创建一个名为styles的变量,变量的值为调用StyleSheet对象的create()方法所产生的返回值。
create()方法的参数是一个对象字面量,要解释它让我们来看看 创建对象的两种方式:

  1. 直接创建Object的实例;
person=new Object();
person.firstname="John";
person.lastname="Doe";
person.age=50;
person.eyecolor="blue";

属性不需要提前声明,赋值的时候会自动创建一个firstname的属性。
上面这种写法容易理解但繁琐,我们可以用对象字面量(literals)作为创建对象实例的替代方案。
var person={firstname:"Tianhang",lastname:"Yu",age:26,eyecolor:"black"}
对象的属性通过propertyName:$propertyValue的形式添加,用,分隔。
这样我们就明白了,create()方法的参数传入的是一个用对象字面量表示的一个对象。basebackgroundactive这些都是这个对象的属性,而他们本身又是一个对象,例如base是一个对象,它的属性分别是widthheight

**字面量(literals)**还有很多,字符串字面量、表达式字面量、函数字面量等等。参见:字面量

  1. 使用对象构造器。- 这种方式以后再说,因为在当前理解React Native的代码过程中现在用不到

JavaScript不使用类。

很多语言教程只告诉你这门语言有什么,却从不告诉你这门语言没有什么,更不告诉你这门语言为什么有什么没有什么,相当糟糕的学习体验。对于一个熟练Objective-C的开发者来说,切换到JS之后我的第一反映是我的类在哪儿?怎么创建一个类?这个代码块是不是类?更不用说很多用JS模拟类概念的开发框架中,经常见到的class关键字。
JavaScript 基于 prototype,而不是基于类的。有关prototype有一大坨东西要说:#here

分号

分号用于分隔 JavaScript 语句。在Javascript的语法规则中,用分号来结束语句是可选的。
至于该不该加分号的问题可以看这里的回答,因为写惯了Ruby和Swift我的结论是能不加都不加(仅代表个人观点,具体应用看个人喜好和大牛们的文章)。
因此上面例子中结尾的;没什么用,以后讲解中React Native示例代码中的分号也不做特殊说明。
不过;在阅读代码中确实有一个用处就是判断到哪里是这一条语句的结尾,因为对于像上面例子和很多JS代码中其实相当长的一个东西其实只是一句代码,注意;的位置和适当添加;可以增加代码的可读性。

更多的Javascript编码规范可以看看Javascript编程风格|阮一峰,至于这里提到的Javascript会自动在末尾加分号的情况不知是不是JS解析引擎在压缩的时候会加上。


到这里为止第一段示例代码讲解完成,随着JS基础掌握的越来越多,后续代码的讲解会逐步加快。

逗号

对象字面量中的属性间要有,,最后一个属性后的,可选。

第二段代码 - Using Styles

<Text style={styles.base} />
<View style={styles.background} />
...
<View style={[styles.base, styles.background]} />
...
<View style={[styles.base, this.state.active && styles.active]} />

最初在JS代码中看到<Text ... />这货我有点紧张,丫不是HTML么?然而事实并不是这样。这货之所以出现在React Native的代码中是因为一个叫做JSX的一个看起来很像 XML 的 JavaScript 语法扩展,最终还是会转化为JS代码输出。深入了解可以参考这里JSX|React

JSX in Reading

应用到理解React Native代码的话可以理解成:任何React Component都可以直接用XML(很像HTML标签)的形式来写,但是对自己定义的Component要注意作用域的问题。就这样。

当JSX中属性的值如果是一个JS Object对象时用{}包起来,并且允许像第三行那样在{}中传递一个JS数组[]

string的话可以直接赋值:var app = <Nav color="blue" />

JSX in Coding

在实际写React代码的时候JSX和HTML标签稍有区别:
React可以渲染HTML标签(strings)和React组件(classes)。
要渲染HTML标签,只需要在JSX里使用小写字母的标签名。

var myDivElement = <div className="foo" / >;
ReactDOM.render(myDivElement, document.getElementById('example'));

要渲染React组件,只需创建一个大写字母开头的本地变量。

var MyComponent = React.createClass({/*...*/})
var myElement = <MyComponent someProperty={true} />;
ReactDOM.render(myElement, document.getElementById('example'))

React的JSX使用大、小写的约定来区分本地组件的类和HTML标签。

你可能已经注意到上面div标签中className属性没有像HTML一样用class这个关键字。原因是JSX就是Javascript代码,一些像classfor这样的JS语法关键字不建议用作XML属性名。作为替代,ReactDOM使用classNamehtmlfor作为对应的属性。

ReactDOM.render是虚拟DOM的渲染方法,后续教程再详细说。

React的类

前面说过Javascript没有使用类,那么为什么有class关键字呢?原因是在面向对象编程的尝试中,人们总结出一些在JS中模拟类的方法,参见阮一峰|Javascript定义类(class)的三种方法
而最新的Javascript语法ES6(ECMAScript 6)中更是提供了class这个基于Objectsprototypes模拟类的语法糖。在ES6中定义一个类的语法如下:

class View {
constructor(options) {
this.model = options.model;
this.template = options.template;
}
render() {
return _.template(this.template, this.model.toObject());
}
}

React库中也大量使用了JS模拟类的技术。

第三段代码 - Pass Styles Around

var List = React.createClass({
propTypes: {
style: View.propTypes.style,
elementStyle: View.propTypes.style,
},
render: function() {
return (
<View style={this.props.style}>
{elements.map((element) =>
<View style={[styles.element, this.props.elementStyle]} />
)}
</View>
);
}
});
// ... in another file ...
<List style={styles.list} elementStyle={styles.listElement} />

React Class

React类是所有React API的入口。React代码可以看做是一个一个的视图组件(React Component)叠加起来,渲染(render)到视图上。

class Component

Component类就是所有React组件的基类。

React组件

React组件创建的方式有两种,这段示例中用到的React.createClass是其中之一(还有一种方式是class x extends React.Component)。
React.createClass可以看作是React组件的构造器,createClass()方法的参数同样是一个JS对象字面量。这个对象非常重要。
在这个对象中必须实现一个render()方法,用于返回组件的视图。在一个对象中方法的声明方式是:render: function() {},使用return ();形式的语句返回值。特殊的,render方法会返回一个视图组件,比如JSX定义的视图组件。
render方法返回值中,给View的style赋值调用了this.props.style这样的结构。

this指针

this指针是Javascript中很重要的一个课题,在这里我们只要知道:对象字面量({})中的this指针指向这个对象。

props

因此this.props就是访问当前对象的props属性,可是当前对象并没有定义props这个属性啊。看了下面这个List组件的使用例子你就明白了:

<List style={styles.list} elementStyle={styles.listElement} />

props就是我们通过JSX使用这个组件时传入的各种属性。
这是一个很方便的语法,但是同样引入了一个问题:外界在使用这个组件的时候,如果知道需要传入哪些属性?这些属性都是什么类型的对象?为了解决这个问题,React引入了我们在代码第二行看到的PropTypes

PropTypes

在这个属性中我们可以显示的指出都有哪些属性、每一个属性的类型、属性是否是必须的(isRequired)等等。

React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
requiredString: React.PropTypes.string.isRequired,
...
}
...
}

有关这个对象的其他属性和方法后续会介绍更多。
现在我们知道了第三段代码中创建了一个新组件List,定义了属性及类型,定义了render()方法。那么这个方法中的视图构成究竟是怎样的呢?我们把render中的代码单独抽离出来:

<View style={this.props.style}>
{elements.map((element) =>
<View style={[styles.elementStyle, this.props.elementStyle]} />
)}
<View />

外层的View我们已经说过,从this.props中取出父视图传入的style,接下来主要关注父视图内嵌的部分。
内嵌的部分是一个JS对象,它是通过循环外部传入的elements数组中的每一项得到的。

map方法

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

array.map(callback[, thisArg])

map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

箭头函数(lambda表达式)

map方法传入的参数其实是一个方法,这是ES6之后JS中lambda表达式的新写法,即箭头函数。

(element) => <View style={[styles.elementStyle, this.props.elementStyle]} />

等价于:

function (element) {
return <View style={[styles.elementStyle, this.props.elementStyle]} />;
}

有关这种JS lambda可以看看InfoQ的翻译文章,FYI. 文章中对lambda表达式中this指针的讲解也非常好。

参考连接

  1. JavaScript 教程|菜鸟教程
  2. An introduction to ES6 classes
  3. React|Top-Level API
  4. React组件的创建
  5. React组件|PropTypes
  6. InfoQ|箭头函数 Arrow Functions
  7. MDN|Array.prototype.map()

评论