对非前端开发者学习React Native来说,可能是最好的JS语法教程。
作为一个iOS工程师,当我第一眼看到React代码的时候,脑海里浮现的到处都是WTF,这货是啥?,这里为啥有分号or等号,这里为啥没有?,这个大括号是做啥的?,这是在定义一个类?or What ever?。由于负责工作内容的原因,JS代码我也经常见,但是读一种语言和编写它完全是两码事,你必须知晓当前编写范例中的全部语法才能真正掌握它,尤其想Javascript这样的语言,而像我这么懒惰的人当然是希望用到某种JS语法的时候再去学习,而不是先花半个月时间啃一本Javascript的语法(我在其他语言上试过但是效果并不好)。于是便有了这篇Javascript的语法教程,专门给开始学习React Native的非前端开发者准备。
谁适合看这篇教程:
- 至少接触过一门静态 or 动态编程语言;
- 正在或者打算开始学习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()
方法的参数是一个对象字面量,要解释它让我们来看看 创建对象的两种方式:
- 直接创建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()
方法的参数传入的是一个用对象字面量表示的一个对象。base
、background
、active
这些都是这个对象的属性,而他们本身又是一个对象,例如base
是一个对象,它的属性分别是width
和height
。
**字面量(literals)**还有很多,字符串字面量、表达式字面量、函数字面量等等。参见:字面量
- 使用对象构造器。- 这种方式以后再说,因为在当前理解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代码,一些像class
和for
这样的JS语法关键字不建议用作XML属性名。作为替代,ReactDOM使用className
和htmlfor
作为对应的属性。
ReactDOM.render
是虚拟DOM的渲染方法,后续教程再详细说。
React的类
前面说过Javascript没有使用类,那么为什么有class
关键字呢?原因是在面向对象编程的尝试中,人们总结出一些在JS中模拟类的方法,参见阮一峰|Javascript定义类(class)的三种方法。
而最新的Javascript语法ES6(ECMAScript 6)中更是提供了class
这个基于Objects
和prototypes
模拟类的语法糖。在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指针的讲解也非常好。