前言
本系列是基于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
"
致谢
如果发现有错误的地方,欢迎各位指出,谢谢!