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>
。