React 基础
React Native 的基础是React, 是在 web 端非常流行的开源 UI 框架。要想掌握 React Native,先了解 React 框架本身是非常有帮助的。本文旨在为初学者介绍一些 react 的入门知识。
本文主要会探讨以下几个 React 的核心概念:
- components 组件
- JSX
- props 属性
- state 状态
如果你想更深一步学习,我们建议你阅读React 的官方文档,它也提供有中文版。
尝试编写一个组件
本文档会用“Cat”这种有个名字和咖啡馆就能开始工作的人畜无害的生物来作为例子。下面是我们的第一个 Cat 组件:
- 函数式组件
- Class 组件
要定义一个Cat
组件,第一步要使用import
语句来引入React
以及React Native
的Text
组件:
import React from 'react';
import { Text } from 'react-native';
然后一个简单的函数就可以作为一个组件:
const Cat = () => {};
这个函数的返回值
就会被渲染为一个 React 元素。这里Cat
会渲染一个<Text>
元素:
const Cat = () => {
return <Text>Hello, I am your cat!</Text>;
};
这里我们还使用了export default
语句来导出这个组件,以使其可以在其他地方引入使用:
const Cat = () => {
return <Text>Hello, I am your cat!</Text>;
};
export default Cat;
Class 组件比函数组件写起来要繁琐一些。
你还需要从 React 中引入Component
:
import React, { Component } from 'react';
定义组件首先要继承(extends)自Component
:
class Cat extends Component {}
Class 组件必须有一个render()
函数,它的返回值会被渲染为一个 React 元素:
class Cat extends Component {
render() {
return <Text>Hello, I am your cat!</Text>;
}
}
和函数组件一样,我们也可以导出 class 组件:
class Cat extends Component {
render() {
return <Text>Hello, I am your cat!</Text>;
}
}
export default Cat;
上面只是导出组件的写法之一。你还可以看看这篇博客整理handy cheatsheet on JavaScript imports and exports整理的各种不同的写法。
下面我们来看看这个return
语句。<Text>Hello, I am your cat!</Text>
是一种简化 React 元素的写法,这种语法名字叫做 JSX。
JSX
React 和 React Native 都使用JSX 语法,这种语法使得你可以在 JavaScript 中直接输出元素:<Text>Hello, I am your cat!</Text>
。React 的文档有一份完整的JSX 指南可供你参考。因为 JSX 本质上也就是 JavaScript,所以你可以在其中直接使用变量。这里我们为猫猫的名字声明了一个变量name
,并且用括号把它放在了<Text>
之中。
``
括号中可以使用任意 JavaScript 表达式,包括调用函数,例如{getFullName("Rum", Tum", "Tugger")}
:
你可以把括号{}
想象成在 JSX 中打开了一个可以调用 JS 功能的传送门!
在 React Native 0.71 版本之前,JSX 语法糖的实质是调用
React.createElement
方法,所以你必须在文件头部引用import React from 'react'
。但在 React Native 0.71 版本之后,官方引入了新的 JSX 转换,可以不用再在文件头部写import React from 'react'
。
自定义组件
你应该已经了解React Native 的核心组件了。 React 使得你可以通过嵌套这些组件来创造新组件。这些可嵌套可复用的组件正是 React 理念的精髓。
例如你可以把Text
和TextInput
嵌入到View
中,React Native 会把它们一起渲染出来:
对开发者的提示
- Android
- Web
如果你熟悉 web 开发,
<View>
和<Text>
应该能让你想起 HTML。你可以把它们看作是应用开发中的<div>
和<p>
标签。
在 Android 上,常见的做法是把视图放入
LinearLayout
,FrameLayout
或是RelativeLayout
等布局容器中来定义子元素如何排列。在 React Native 中,View
使用弹性盒模型(Flexbox)来为子元素布局。详情请参考使用 Flexbox 布局。
这样你就可以在别处通过<Cat>
来任意引用这个组件了:
我们把包含着其他组件的组件称为父组件或父容器。这里Cafe
是一个父组件,而每个Cat
则是子组件。
你的咖啡店里,想养多少只猫都行!注意每只<Cat>
渲染的都是不同的元素——你可以使用不同的 props 属性来定制它们。
Props 属性
Props 是“properties”(属性)的简写。Props 使得我们可以定制组件。比如可以给每只<Cat>
一个不同的name
:
React Native 的绝大多数核心组件都提供了可定制的 props。例如,在使用Image
组件时,你可以给它传递一个source
属性,用来指定它显示的内容:
Image
有很多不同的 props,style
也是其中之一,它接受对象形式的样式和布局键值对。
请留意我们在指定
style
属性的宽高时所用到的双层括号{{ }}
。在 JSX 中,引用 JS 值时需要使用{}
括起来。在你需要传递非字符串值(比如数组或者数字)的时候会经常用到这种写法:<Cat food={["fish", "kibble"]} /> age={2}
。然而我们在 JS 中定义一个对象时,本来也需要用括号括起来:{width: 200, height: 200}
。因此要在 JSX 中传递一个 JS 对象值的时候,就必须用到两层括号:{{width: 200, height: 200}}
。
使用核心组件Text
, Image
以及View
搭配 props 已经可以做不少东西了!但是如果想要做一些用户交互,那我们还需要用到状态(state)。
State 状态
如果把 props 理解为定制组件渲染的参数, 那么state就像是组件的私人数据记录。状态用于记录那些随时间或者用户交互而变化的数据。状态使组件拥有了记忆!
按惯例来说,props 用来配置组件的第一次渲染(初始状态)。state 则用来记录组件中任意可能随时间变化的数据。下面示例的情景发生在一个猫咪咖啡馆中,两只猫咪正嗷嗷待哺。它们的饥饿程度会随着时间变化(相对地,它们的名字就不会变化),因此会记录在状态中。示例中还有一个喂食按钮,一键干饭,扫除饥饿状态!
- 函数式组件
- Class 组件
你可以使用React 的useState
Hook来为组件添加状态。Hook (钩子)是一种特殊的函数,可以让你“钩住”一些 React 的特性。例如useState
可以在函数组件中添加一个“状态钩子”,在函数组件重新渲染执行的时候能够保持住之前的状态。要了解更多,可以阅读React 中有关 Hook 的文档。
首先要从 react 中引入useState
:
import React, { useState } from 'react';
然后可以通过在函数内调用useState
来为组件声明状态。在本示例中 useState
创建了一个 isHungry
状态变量:
const Cat = (props) => {
const [isHungry, setIsHungry] = useState(true);
// ...
};
你可以使用
useState
来记录各种类型的数据: strings, numbers, Booleans, arrays, objects。例如你可以这样来记录猫咪被爱抚的次数:const [timesPetted, setTimesPetted] = useState(0)
。useState
实质上做了两件事情:
- 创建一个“状态变量”,并赋予一个初始值。上面例子中的状态变量是
isHungry
,初始值为true
。 - 同时创建一个函数用于设置此状态变量的值——
setIsHungry
。
取什么名字并不重要。但脑海中应该形成这样一种模式:[<取值>, <设值>] = useState(<初始值>)
.
下面我们添加一个按钮Button
组件,并给它一个onPress
的 prop:
<Button
onPress={() => {
setIsHungry(false);
}}
//..
/>
现在当用户点击按钮时,onPress
函数会被触发,从而调用setIsHungry(false)
。此时状态变量isHungry
就被设为了false
。当isHungry
为 false 的时候,Button
的disabled
属性就变成了true
,其title
也相应变化:
<Button
//..
disabled={!isHungry}
title={isHungry ? 'Pour me some milk, please!' : 'Thank you!'}
/>
你可能注意到虽然
isHungry
使用了常量关键字const,但它看起来还是可以修改!简单来说,当你调用setIsHungry
这样的设置状态的函数时,其所在的组件会重新渲染。此处这一整个Cat
函数都会从头重新执行一遍。重新执行的时候,useState
会返回给我们新设置的值。
最后再把猫咪放进Cafe
组件:
const Cafe = () => {
return (
<>
<Cat name="Munkustrap" />
<Cat name="Spot" />
</>
);
};
老式的 class 组件在使用 state 的写法上有所不同:
再次强调,对于 class 组件始终要记得从 React 中引入Component
:
import React, { Component } from 'react';
在 class 组件中, state 以对象的形式存放:
export class Cat extends Component {
state = { isHungry: true };
//..
}
和使用this.props
获取 props 一样,在组件中获取状态也是通过this.state
:
<Text>
I am {this.props.name}, and I am
{this.state.isHungry ? ' hungry' : ' full'}!
</Text>
要修改状态中的值,只需给this.setState()
传入一个对象,包含要修改的键值对即可:
<Button
onPress={() => {
this.setState({ isHungry: false });
}}
/>
不要直接给组件 state 赋值(比如
this.state.hunger = false
)来修改状态。使用this.setState()
方法才能让 React 知悉状态的变化,从而触发重渲染。直接修改状态变量可能会使界面无法响应!
当this.state.isHungry
为 false 时,Button
的disabled
属性随之被设置为false
,它的title
也相应变化:
<Button
// ..
disabled={!this.state.isHungry}
title={
this.state.isHungry
? 'Pour me some milk, please!'
: 'Thank you!'
}
/>
最后,把你的猫放到一个咖啡店Cafe
组件中:
class Cafe extends Component {
render() {
return (
<>
<Cat name="Munkustrap" />
<Cat name="Spot" />
</>
);
}
}
export default Cafe;
注意到上面的
<>
和</>
了吗? 这一对 JSX 标签称为Fragments(片段)。由于 JSX 的语法要求根元素必须为单个元素,如果我们需要在根节点处并列多个元素,在此前不得不额外套一个没有实际用处的View
。但有了 Fragment 后就不需要引入额外的容器视图了。
现在你应该已经差不多了解 React 和 React Native 的核心组件与思想了。下面可以试着深入学习一些核心组件的用法,比如如何处理文本输入<TextInput>
。