ReacNative学习笔记1——基础

React Native看起来像React,只不过其基础组件是原生组件而非Web组件。要理解React Native需要先了解一些React的概念,比如JSX语法组件state状态以及props属性

示例:

import React, { Component } from 'react';
import { Text, View } from 'react-native';

export default class HelloWorldApp extends Component {
  render() {
    return (
        <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
          <Text>Hello, world!</Text>
        </View>
    );
  }
}

替换App.js,重新运行可看效果。
以上代码是ES2015(也叫ES6)的javascript标准(目前并非所有浏览器都支持,所以Web开发中很少使用,但是React Native内置了对其的支持)。
示例中<View><Text>Hello world!</Text></View>叫做JSX,一种在Javascript中嵌入XML 结构的语法。<View><Text>是React Native中的组件,其中<Text>专门用来显示文本,而<View>类似HTML中的div或者span这样的容器。

组件(Component)

上面的HelloWorldApp为一个组件(Component)。在我看来就是可复用的UI片段(类比android 中的View和ViewGroup),App界面就是由这些组件搭建而成,也可以嵌套在其他组件中。组件唯一要做的就是在render方法中返回一些用于渲染结构的JSX语句。

属性(Props)

可以定制组件的各种参数叫做属性props。类比Android中View/View Group或者自定义控件中用来控制UI显示效果的各种参数。
示例:

import React, { Component } from 'react';
import { Image } from 'react-native';

export default class Bananas extends Component {
  render() {
    let pic = {
      uri: 'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'
    };
    return (
      <Image source={pic} style={{width: 193, height: 110}} />
    );
  }
}

其中sourcestyle就是<Image>组件的属性props。它的值如果是JavaScript变量或者表达式,需要用{}括起来。当然,并不限于JavaScript变量或表达式类型。

自定义组件也可以使用props,只需要在render函数中引用this.props,然后按需处理。比如:

import React, { Component } from 'react';
import { Text, View } from 'react-native';

class Greeting extends Component {
  render() {
    return (
      <View style={{alignItems: 'center'}}>
        <Text>Hello {this.props.name}!</Text>
      </View>
    );
  }
}

export default class LotsOfGreetings extends Component {
  render() {
    return (
      <View style={{alignItems: 'center'}}>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

示例自定义了Greeting组件,其中定义了name属性,可以控制显示不同的文本,由LotsOfGreetings组件复用。

状态(State)

同属性类似也是控制组件的。所不同的是props在组件的生命周期中不再改变,而对于可变的数据,需要使用state
一般来说,需要在constructor中初始化state,然后在需要修改时调用setState方法。

import React, { Component } from 'react';
import { Text, View } from 'react-native';

class Blink extends Component {
  constructor(props) {
    super(props);
    this.state = { isShowingText: true };

    // 每1000毫秒对showText状态做一次取反操作
    setInterval(() => {
      this.setState(previousState => {
        return { isShowingText: !previousState.isShowingText };
      });
    }, 1000);
  }

  render() {
    // 根据当前showText的值决定是否显示text内容
    if (!this.state.isShowingText) {
      return null;
    }

    return (
      <Text>{this.props.text}</Text>
    );
  }
}

export default class BlinkApp extends Component {
  render() {
    return (
      <View>
        <Blink text='I love to blink' />
        <Blink text='Yes blinking is so great' />
        <Blink text='Why did they ever take this out of HTML' />
        <Blink text='Look at me look at me look at me' />
      </View>
    );
  }
}

示例通过定时修改组件是否显示文本状态,然后调用setState重复调用render方法来控制文本的显示与否,从而实现闪烁效果。

注意几点:

  • 一切界面变化都是状态state变化
  • state的修改必须通过setState()方法
    • this.state.likes = 100; // 这样的直接赋值修改无效!
    • setState 是一个 merge 合并操作,只修改指定属性,不影响其他属性
    • setState 是异步操作,修改不会马上生效

样式(Style)

类比CSS和Android中的style。用来控制组件的显示外观。
样式还是用JavaScript来是书写。所有的核心组件都接受名为style的属性。样式命名参照CSS命名,只不过改成了驼峰命名法,例如将background-color改成backgroundColor
style属性可以是普通的JavaScript对象,也可以是一个样式数组(数组中位置靠后的样式对象比前面的更高),这种方式可以实现样式的继承。

通常用StyleSheet.create来集中定义组件的样式。

import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View } from 'react-native';

export default class LotsOfStyles extends Component {
  render() {
    return (
      <View>
        <Text style={styles.red}>just red</Text>
        <Text style={styles.bigBlue}>just bigBlue</Text>
        <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
        <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  bigBlue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});
  • 指定宽度和高度
    • 简单点,在style中指定固定的width和height。
    • 弹性宽高:flex,类似Android中LinearLayout的weight属性。指定占用剩余空间的权重。
    • 如果同时没有设定flex和width/height,则控件不显示。

固定宽高

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

export default class FixedDimensionsBasics extends Component {
  render() {
    return (
      <View>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 100, height: 100, backgroundColor: 'skyblue'}} />
        <View style={{width: 150, height: 150, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
}

弹性宽高:

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

export default class FlexDimensionsBasics extends Component {
  render() {
    return (
      // 试试去掉父View中的`flex: 1`。
      // 则父View不再具有尺寸,因此子组件也无法再撑开。
      // 然后再用`height: 300`来代替父View的`flex: 1`试试看?
      <View style={{flex: 1}}>
        <View style={{flex: 1, backgroundColor: 'powderblue'}} />
        <View style={{flex: 2, backgroundColor: 'skyblue'}} />
        <View style={{flex: 3, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
}

布局

React Native使用flexbox规则来指定某个组件的子元素的布局。FlexBox可以在不同屏幕尺寸上提供一致的布局结构。
一般来说,使用flexDirectionalignItemsjustifyContent三个样式属性就可以满足大多数布局需求。

Flex Direction

组件的style中指定flexDirection可以决定子元素在布局的排列方向。默认为column,表示垂直方向(子元素从上往下排列)。而row表示水平方向(子元素从左向右开始排列)。可以类比Android中LinearLayout中的orientation。取值还有row-reversecolumn-reverse

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

export default class FlexDirectionBasics extends Component {
  render() {
    return (
      // 尝试把`flexDirection`改为`column`看看
      <View style={{flex: 1, flexDirection: 'row'}}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

Justify Content

style中的justifyContent决定子元素在布局中的排列方式。类似Android中ViewGroup的gravity,但是更强大。可选值有:

import React, { Component } from 'react';
import { Text, View, Image, StyleSheet} from 'react-native';

const styles = StyleSheet.create({  
    box: {
        width: 100,
        height: 80,
    },
    
});

export default class HelloWorldApp extends Component {
  render() {
    return (
        <View style={{flex: 1, justifyContent: 'space-around'}}>
                <View style={[{backgroundColor: 'red'}, styles.box]} />
                <View style={[{backgroundColor: 'green'}, styles.box]} />
                <View style={[{backgroundColor: 'blue'}, styles.box]} />
        </View>
    );
  }
}
  • flex-start:沿主轴起始端排列


    flex_start.jpg
  • flex-end:沿主轴末尾端排列


    flex_end.jpg
  • center:沿主轴居中排列


    center.jpg
  • space-evenly: 每个ITEM间距一样,并且距离start和end边界也一样


    space_evenly.jpg
  • space-between:start和end的item分别距离start和bottom的间距为0,然后每个ITEM之间间距一样。


    space_between.jpg
  • space-around:每个ITEM虚拟一个外部空间,这个空间相等,然后每个ITEM在这个空间中居中。(原谅我不知道怎么描述)


    space_around.jpg

Align Items

在组件的 style 中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-startcenterflex-end以及stretch

注意:要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。以下面的代码为例:只有将子元素样式中的width: 50去掉之后,alignItems: 'stretch'才能生效。

处理文本输入

TextInput组件,类比Android的EditText,允许用户输入。需要重点关注onChangeText属性,接收一个函数,当文本变化时被调用。还有一个onSubmitEditing属性,会在文本被提交后(用户按下软键盘上的提交建)调用。
假如我们要实现当用户输入时,实时将其以单词为单位翻译为另一种文字。我们假设这另一种文字来自某个吃货星球,只有一个单词: 🍕。所以"Hello there Bob"将会被翻译为"🍕🍕🍕"。

import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';

export default class PizzaTranslator extends Component {
  constructor(props) {
    super(props);
    this.state = {text: ''};
  }

  render() {
    return (
      <View style={{padding: 10}}>
        <TextInput
          style={{height: 40}}
          placeholder="Type here to translate!"
          onChangeText={(text) => this.setState({text})}
        />
        <Text style={{padding: 10, fontSize: 42}}>
          {this.state.text.split(' ').map((word) => word && '🍕').join(' ')}
        </Text>
      </View>
    );
  }
}

处理触摸事件

Button组件,onPress属性

import React, { Component } from 'react';
import { Alert, AppRegistry, Button, StyleSheet, View } from 'react-native';

export default class ButtonBasics extends Component {
  _onPressButton() {
    Alert.alert('You tapped the button!')
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={styles.buttonContainer}>
          <Button
            onPress={this._onPressButton}
            title="Press Me"
          />
        </View>
        <View style={styles.buttonContainer}>
          <Button
            onPress={this._onPressButton}
            title="Press Me"
            color="#841584"
          />
        </View>
        <View style={styles.alternativeLayoutButtonContainer}>
          <Button
            onPress={this._onPressButton}
            title="This looks great!"
          />
          <Button
            onPress={this._onPressButton}
            title="OK!"
            color="#841584"
          />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
   flex: 1,
   justifyContent: 'center',
  },
  buttonContainer: {
    margin: 20
  },
  alternativeLayoutButtonContainer: {
    margin: 20,
    flexDirection: 'row',
    justifyContent: 'space-between'
  }
})

// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => ButtonBasics);

Button组件样式貌似是固定的,如果你要自定义的话,一般是基于TouchableOpacityTouchableOpacity来定制。

具体使用哪种组件,取决于你希望给用户什么样的视觉反馈:

  • 一般来说,你可以使用TouchableHighlight来制作按钮或者链接。注意此组件的背景会在用户手指按下时变暗。
  • 在 Android 上还可以使用TouchableNativeFeedback,它会在用户手指按下时形成类似墨水涟漪的视觉效果。
  • TouchableOpacity会在用户手指按下时降低按钮的透明度,而不会改变背景的颜色。
  • 如果你想在处理点击事件的同时不显示任何视觉反馈,则需要使用TouchableWithoutFeedback

ScrollView

类似Android中的ScrollView,所不同的是其可以直接包含多个子控件。默认为垂直方向,设置horizontal属性为true可设置为水平方向。

import React, { Component } from 'react';
import { AppRegistry, ScrollView, Image, Text } from 'react-native';

export default class IScrolledDownAndWhatHappenedNextShockedMe extends Component {
  render() {
      return (
        <ScrollView horizontal={false}> 
          <Text style={{fontSize:96}}>Scroll me plz</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:96}}>If you like</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:96}}>Scrolling down</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:96}}>What's the best</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:96}}>Framework around?</Text>
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
          <Text style={{fontSize:80}}>React Native</Text>
        </ScrollView>
    );
  }
}

ScrollView下所有元素都会被渲染,所以适合数量不多的滚动元素。如果要显示较长的滚动列表,请使用FlatList组件。

长列表

React Native提供了几种适合展示长列表数据的组件,一般选用FlatList或SectionList。
FlatList和Android的ListView就差不多了。适合长列表,且元素个数可以增删。
data属性作为数据源,renderItem用于渲染单个item,这不就是Android中Adapter的getView方法么。

import React, { Component } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';

export default class FlatListBasics extends Component {
  render() {
    return (
      <View style={styles.container}>
        <FlatList
          data={[
            {key: 'Devin'},
            {key: 'Jackson'},
            {key: 'James'},
            {key: 'Joel'},
            {key: 'John'},
            {key: 'Jillian'},
            {key: 'Jimmy'},
            {key: 'Julie'},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 22
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})

如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么SectionList将是个不错的选择

网络

React Native 提供了和 web 标准一致的Fetch API

  • 发请求:
fetch('https://mywebsite.com/mydata.json');
fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  }),
});  
fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'key1=value1&key2=value2',
});
  • 处理响应
function getMoviesFromApiAsync() {
  return fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });
}
// 注意这个方法前面有async关键字
async function getMoviesFromApi() {
  try {
    // 注意这里的await语句,其所在的函数必须有async关键字声明
    let response = await fetch(
      'https://facebook.github.io/react-native/movies.json',
    );
    let responseJson = await response.json();
    return responseJson.movies;
  } catch (error) {
    console.error(error);
  }
}

示例:

import React from 'react';
import { FlatList, ActivityIndicator, Text, View  } from 'react-native';

export default class FetchExample extends React.Component {

  constructor(props){
    super(props);
    this.state ={ isLoading: true}
  }

  componentDidMount(){
    return fetch('https://facebook.github.io/react-native/movies.json')
      .then((response) => response.json())
      .then((responseJson) => {

        this.setState({
          isLoading: false,
          dataSource: responseJson.movies,
        }, function(){

        });

      })
      .catch((error) =>{
        console.error(error);
      });
  }



  render(){

    if(this.state.isLoading){
      return(
        <View style={{flex: 1, padding: 20}}>
          <ActivityIndicator/>
        </View>
      )
    }

    return(
      <View style={{flex: 1, paddingTop:20}}>
        <FlatList
          data={this.state.dataSource}
          renderItem={({item}) => <Text>{item.title}, {item.releaseYear}</Text>}
          keyExtractor={(item, index) => item.id}
        />
      </View>
    );
  }
}
var request = new XMLHttpRequest();
request.onreadystatechange = (e) => {
  if (request.readyState !== 4) {
    return;
  }

  if (request.status === 200) {
    console.log('success', request.responseText);
  } else {
    console.warn('error');
  }
};

request.open('GET', 'https://mywebsite.com/endpoint/');
request.send();
  • 支持websocket
var ws = new WebSocket('ws://host.com/path');

ws.onopen = () => {
  // connection opened
  ws.send('something'); // send a message
};

ws.onmessage = (e) => {
  // a message was received
  console.log(e.data);
};

ws.onerror = (e) => {
  // an error occurred
  console.log(e.message);
};

ws.onclose = (e) => {
  // connection closed
  console.log(e.code, e.reason);
};

``

推荐阅读更多精彩内容