前言
本系列是基于React Native
版本号0.44.3
写的,最初学习React Native
的时候,完全没有接触过React
和JS
,本文的目的是为了给那些JS
和React
小白提供一个快速入门,让你们能够在看React Native
语法的时候不那么费劲,有过前端开发经验的可以直接忽略。
什么是React
React
是一个JavaScript
框架,用来开发web
应用。Web
应用开发中,比较流行的有三个框架:
- react
- angular
- vue
从名字上,就能看到react native
是基于React
(都是Facebook
出品)。React
的设计思想是:
Declarative(交互式的)
应用都是基于状态的,应用会随着数据的变化切换到不同的状态,
React
将这种状态抽象为一个个View
,这样状态改变后,利用React
就在不同
的View
之间切换。这样,让代码更清晰可预测,也方便测试。Component-Based(基于组件的)
把管理状态的
View
封装成Component
,然后再把这些Component
组合到一起来实现复杂的UI
。Learn Once, Write Anywhere(一次编写,多处编译)
React
支持Web
开发,Server
开发(Node
),同样也支持本文提到的App
开发(React Native
)。
JSX
JSX
是JavaScript
语言的扩展,它并不改变JS
本身语法。使用起来类型XML
,React
会对JSX
的代码进行编译,生成JavaScript
代码,用来描述React
中的Element
如何渲染。
上篇文章创建的项目中,index.ios.js
里面的这段代码就是JSX
语法:
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</View>
);
}
其中,
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
会被编译成
React.createElement(
Text,
{style: styles.welcom},
'Welcome to React Native!'
)
注意:使用JSX
,一定要在scope
中,能够访问到React
和对应的Element
。比如刚刚的例子,在代码的最上面看到了这样的import
:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';
tips: jsx编译结果在线查看
如果你的标签是空的,可以用/>
进行close
,比如:<CustomComponent style={styles.welcome} />
大小写
JSX
对大小写开头是敏感的
- 小写字母开头会被认为是
html
内置的标签。比如div
- 大写字母开头会被认为是自己创建的或者
import
的component
所以,自定义的component
必须是大写字母开头
举个?:
如果上文中的Text
改成小写,
<text style={styles.welcome}>
Welcome to React Native!
</text>
会被编译成:
React.createElement(
"text",
{ style: styles.welcome },
"Welcome to React Native!"
);
React
在解析的时候,会认为这和div
类似,是html
内置标签,引起错误。
JS代码
JSX
中的JS
表达式要用{}
括起来,不要加引号,加引号后React
会认为是字符串。
比如,你可以这么写:
<Text style={styles.welcome}>
{"Welcome" + "to" + "React Native"}
</Text>
Children
根据以下的代码:
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</View>
可以看出,在JSX
中可以嵌套Element
形成一种层次结构,这种层次结构可以动态生成,例如:
render() {
var textElement = <Text style={styles.welcome}>{mainText}</Text>
return (
<View style={styles.container}>
{textElement}
</View>
);
}
Element
Element
是你在屏幕上想看到的东西,在React
中,一个element
就是一个对象。
在
React
中,element
是不变的。如果用户想要看到变化,就需要渲染下一帧。
那么你可能会问,这样效率不是很低么?
事实上,
React
只会更新变化的部分,对于不变的视图,是不会重新渲染的。
React
强调函数式编程,不可变状态是函数式编程的核心思想之一。不可变状态能够让你的代码更容易编写,测试和维护。一个不可变的函数,在输入一定的时候,输出一定是一样的。
Component
在React Native
开发中,component
是一个非常重要的概念,它类似于iOS
的UIView
或者Android
中的view
,将视图分成一个个小的部分。React Native
中,我们通常采用ES6 class来定义一个Component
。
比如上面的代码:
export default class Hello extends Component {
render(){
// ...
}
}
其中,render()
是实际的渲染函数,通常,使用JSX
来返回想要看到的视图。React Native
中的Component
都是原生的Component
,通过JS bridge
来调用原生的Component
来渲染。
这些Component
分为两种:
- iOS/Android通用的,比如:
Navigator
、Text
、Image
等等; - 平台独有的,比如:
NavigatorIOS
、ProgressBarAndroid
等等;
State/props
React
的Component
有两个内置参数对象
props
,由React
自动初始化,包含了传递给一个Component
的参数。state
,包含的参数对象应当用在render
函数中,用作渲染。调用this.setState()
会触发上文提到的Component
重新渲染。
初始化
比如,我们对本文代码进行修改,新建一个Component
:
class Scott extends Component {
render(){
return (
<Text style={styles.welcome}>
欢迎来到{this.props.name}博客学习RN
</Text>
);
}
}
然后,我们使用这个自定义的Component
:
export default class Hello extends Component {
render() {
return (
<View style={styles.container}>
<Scott name={"scott"}/>
</View>
);
}
}
保存文件,选中模拟器,command
+ R
刷新一下,就能看到如下界面:
通过这个例子,如何对Component
初始化进行传值就已经很清楚了:
<Scott name={"scott"}/>
初始化的时候,通过JSX
的参数来传值- 在
Scott
内部,通过this.props.name
来访问这个值
修改视图状态
React
中,修改视图状态是通过this.setState
触发render
重新调用,进而修改视图状态。
我们继续修改上述代码,添加一个构造函数,对state
进行初始化,然后在Scott
初始化的时候,通过this.state.name
获取到值。
在最上面的import
中,我们导入TouchableOpacity
,然后在点击事件中,我们调用this.setState
更新显示的文字:
export default class Hello extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {name: "Jack"};
}
_onPressText(){
this.setState({name: "scott"})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => this._onPressText()}>
<Scott name={this.state.name}/>
</TouchableOpacity>
</View>
);
}
}
保存,选中模拟器,command
+ R
刷新一下,点击屏幕文字,效果如下:
setState 注意事项
不要直接修改
state
这样并不会触发重新渲染:
this.setState.name = "scott"
setState
修改可能是异步的React
有可能会对多个this.setState
进行收集,然后一起更新UI。所以,不要直接依赖上一个状态的结果。所以,这样是不对的:this.setState({ counter: this.state.counter + this.props.number });
如果要依赖于上一个状态,使用
this.setState
第二个模式:this.setState(function(prevState, props){ return { counter: prevState.counter + props.number }; });
setState
是增量更新比如:
class Scott extends Component { render(){ return ( <Text style={styles.welcome}> 点击注意看Lucy是否变成Scott: {this.props.name} </Text> ); } } export default class Hello extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = {firstName:"Lucy", lastName:"Tom"}; } _onPressText(){ this.setState({firstName:"Scott"}) } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={() => this._onPressText()}> <Scott name={this.state.firstName + this.state.lastName}/> </TouchableOpacity> </View> ); } }
可以看到,点击文字之后,通过
this.setState({firstName:"Scott"})
只是修改了
firstName
,lastName
没有做任何变化。tips: 上文的
onPress
采用了js
中的箭头函数,除了箭头函数之外,也可以用function
本身传入:export default class Hello extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = {firstName:"Lucy", lastName:"Tom"}; this._onPressText = this._onPressText.bind(this); } _onPressText(){ this.setState({firstName:"Scott"}) } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={this._onPressText}> <Scott name={this.state.firstName + this.state.lastName}/> </TouchableOpacity> </View> ); } }
注意这一行:
this._onPressText = this._onPressText.bind(this);
因为
JS
中,class
的函数默认没有bind。需要调用bind
来把this
传入_onPressText
。
组件生命周期
- 任何一个组件都是有生命周期的,我们经常需要在组件的生命周期中做一些事情,比如创建组件的时候或者组件销毁的时候。
- 组件生命周期大致分为三个阶段,实例化阶段,运行阶段,销毁阶段。
创建阶段
constructor
- 什么时候调用:在组件初始化的时候调用
- 作用:初始化state
componentWillMount
- 什么时候调用:即将加载组件的时候调用
- 作用:在render之前做事情
render
- 什么时候调用:渲染组件的时候调用
- 作用:通过这个方法渲染界面
componentDidMount
- 什么时候调用:组件渲染完成之后调用
- 作用:在render之后做事情,比如发送请求
tip:注意点:
constructor
、componentWillMount
、componentDidMount
只会调用一次
更新阶段
componentWillReceiveProps
- 什么时候调用:每次传入
Props
的时候就会调用 - 作用:拦截
Props
- 什么时候调用:每次传入
shouldComponentUpdate
- 什么时候调用:每次
Props
或者State
改变就会调用 - 作用:控制界面是否刷新
- 什么时候调用:每次
componentWillUpdate
- 什么时候调用:组件即将更新的时候调用
- 作用:在
render
更新前做事情
componentDidUpdate
- 什么时候调用:组件更新完成之后调用
- 作用:在
render
更新后做事情
tips:注意点:绝对不要在
componentWillUpdate
,componentDidUpdate
中调用this.setState
方法,否则将导致无限循环调用,在componentWillReceiveProps
,shouldComponentUpdate
可以。
销毁阶段
componentWillUnmount
- 什么时候调用:组将即将销毁的时候调用
- 作用:移除观察者,清空数据
举个例子
我们依旧修改以前的代码,给Scott
这个Component
添加上这些方法,最后代码是这样:
class Scott extends Component {
// 构造
constructor(props) {
super(props);
console.log("constructor")
}
componentWillMount() {
console.log("componentWillMount")
}
componentDidMount() {
console.log("componentDidMount")
}
shouldComponentUpdate() {
console.log("shouldComponentUpdate")
return true
}
componentWillReceiveProps() {
console.log("componentWillReceiveProps")
}
componentWillUpdate(){
console.log("componentWillUpdate")
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
componentWillUnmount() {
console.log("componentWillUnmount")
}
render() {
console.log("render")
return (
<Text style={styles.welcome}>
点击注意看Lucy是否变成Scott:
{this.props.name}
</Text>
);
}
}
export default class Hello extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {firstName:"Lucy", lastName:"Tom"};
this._onPressText = this._onPressText.bind(this);
}
_onPressText(){
this.setState({firstName:"Scott"})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._onPressText}>
<Scott name={this.state.firstName + this.state.lastName}/>
</TouchableOpacity>
</View>
);
}
}
保存代码,选择模拟器,command
+ R
刷新一下界面,然后到Xcode
控制台看输出结果,应该是如下图:
我们点击屏幕,触发一下更新,然后可以看到控制台输出结果:
tips:
xcode
控制台会每隔一秒输出__nw_connection_get_connected_socket_block_invoke 2 Connection has no connected handler
, 解决办法:edit scheme
->Run
->Arguments
->Environment Variables
->Add
->Name: "OS_ACTIVITY_MODE"
,Value:"disable
"
致谢
如果发现有错误的地方,欢迎各位指出,谢谢!