在本教程中,我们将使用React Native创建一个新闻阅读器应用程序。 在这个由两部分组成的系列文章中,我将假定这不是您的第一个React Native应用程序,并且在设置机器和在设备上运行该应用程序时,我不会赘述。 也就是说,我将详细解释实际的开发过程。

即使我们将部署到Android,本教程中使用的代码也应在iOS上运行。 这是最终结果。



react native开发android app react native开发 阅读器_javascript


您可以在GitHub找到本教程中使用的源代码。

先决条件

如果您不熟悉React Native并且尚未设置机器,请务必查看React Native文档的入门指南 ,或阅读Envato Tuts +上Ashraff 的入门教程 。 如果要部署到Android或安装Xcode和SDK for iOS, 不要忘记安装Android SDK

完成后,使用npm 安装NodeJS和React Native命令行工具。

npm install -g react-native-cli

1.项目设置

我们现在准备构建项目。 在开始之前,我想简要介绍一下该项目的组合方式。 我们创建两个自定义组件:

  • 呈现新闻项目的NewsItems
  • WebPage ,当用户点击新闻项时呈现网页

然后将它们导入到Android( index.android.js )和iOS( index.ios.js )的主入口点文件中。 这就是您现在需要知道的。

步骤1:建立新应用程式

首先导航到您的工作目录。 在该目录中打开一个新的终端窗口,然后执行以下命令:

react-native init HnReader

这将创建一个名为HnReader的新文件夹,其中包含构建应用程序所需的文件。

React Native已经带有一些默认组件,但也有其他开发人员构建的自定义组件。 您可以在react.parts网站上找到它们。 不过,并非所有组件都可以在Android和iOS上运行。 甚至某些默认组件也不是跨平台的。 这就是为什么在选择组件时必须小心,因为它们在每个平台上可能有所不同,或者在每个平台上可能无法正常工作。

最好进入要使用的组件的GitHub存储库的问题页面,并搜索android支持ios支持以快速检查该组件是否在两种平台上均有效。

步骤2:安装依赖项

我们将要构建的应用程序依赖于一些第三方库和React组件。 您可以通过在工作目录的根目录中打开package.json来安装它们。 将以下内容添加到package.json中

{
  "name": "HnReader",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "react-native start"
  },
  "dependencies": {
    "lodash": "^4.0.1",
    "moment": "^2.11.1",
    "react-native": "^0.18.1",
    "react-native-button": "^1.3.1",
    "react-native-gifted-spinner": "0.0.3"
  }
}

接下来,在工作目录中打开一个终端窗口,然后执行npm install来安装package.json中指定的依赖项。 这是每个库在项目中的功能的简要说明:

  • lodash用于截断字符串。 这可能有点过大,但是您只需编写更少的代码行就意味着更少的责任。
  • 瞬间用于确定本地存储中的新闻项目是否已经存在一天。
  • react-native是React Native框架。 默认情况下,在您较早执行react-native init时会安装该程序。
  • react-native-button是用于创建按钮的react native组件。
  • 发出网络请求时, react-native-gifted-spinner用作活动指示器。

2.主要组成部分

如前所述,所有React Native项目的入口都是index.android.jsindex.ios.js 。 这是本节的重点。 将这些文件的内容替换为以下内容:

'use strict';
var React = require('react-native');

var {
  AppRegistry,
  StyleSheet,
  Navigator
} = React;


var NewsItems = require('./components/news-items');
var WebPage = require('./components/webpage');

var ROUTES = {
  news_items: NewsItems,
  web_page: WebPage
};

var HnReader = React.createClass({  

  renderScene: function(route, navigator) {

    var Component = ROUTES[route.name];
    return (
        <Component route={route} navigator={navigator} url={route.url} />
    );
  },

  render: function() {
    return (
      <Navigator 
        style={styles.container} 
        initialRoute={{name: 'news_items', url: ''}}
        renderScene={this.renderScene}
        configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }} />
    );

  },


});


var styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

AppRegistry.registerComponent('HnReader', () => HnReader);

让我分解一下。 首先,我们使用use script指令启用严格模式。 这使解析器可以对您的代码进行更多检查。 例如,如果您初始化变量而未添加var关键字,它将抱怨。

'use strict';

接下来,我们导入React Native框架。 这使我们可以创建自定义组件并向应用程序添加样式。

var React = require('react-native');

然后,我们从React对象中提取所需的所有功能。

var {
  AppRegistry,
  StyleSheet,
  Navigator
} = React;

如果您不熟悉ES6(ECMAScript 6),则以上代码段与以下代码相同:

var AppRegistry = React.AppRegistry;
var StyleSheet = React.StyleSheet;
var Navigator = React.Navigator;

ES6中引入了语法糖,以简化将对象属性分配给变量的过程。 这称为解构分配

这是我们提取的每个属性的简要说明:

  • AppRegistry用于注册应用程序的主要组件。
  • StyleSheet用于声明组件要使用的样式。
  • Navigator用于在应用程序的不同页面之间切换。

接下来,我们导入应用程序使用的自定义组件。 我们将在以后创建这些。

var NewsItems = require('./components/news-items');
var WebPage = require('./components/webpage');

创建一个ROUTES变量,并使用上述两个组件作为其属性的值来分配一个对象。 这使我们可以通过引用我们定义的每个键来显示组件。

var ROUTES = {
  news_items: NewsItems,
  web_page: WebPage
};

通过从React对象调用createClass方法来创建应用程序的主要组件。 createClass方法接受一个对象作为其参数。

var HnReader = React.createClass({  
    ...
});

对象内部是renderScene方法,每当路线更改时都会调用该方法。 routenavigator器作为该方法的参数传递。 route包含有关当前路线的信息(例如,路线名称)。

navigator包含可用于在不同路线之间navigator方法。 在renderScene方法内部,我们通过将当前路由的名称传递给ROUTES对象来获取要渲染的组件。 接下来,我们渲染组件,并将routenavigatorurl作为属性传递。 稍后,您将看到如何在每个组件中使用它们。 现在,请记住,当您要将数据从主组件传递到子组件时,您要做的就是添加一个新属性,并将要传递的数据用作值。

renderScene: function(route, navigator) {

    var Component = ROUTES[route.name]; //get the component for this specific route

    //render the component and pass along the route, navigator and the url
    return (
        <Component route={route} navigator={navigator} url={route.url} />
    );
},

render方法是创建组件时必需的方法,因为它负责渲染组件的用户界面。 在此方法中,我们呈现了Navigator组件并传递了一些属性。

render: function() {
    return (
      <Navigator 
        style={styles.container} 
        initialRoute={{name: 'news_items', url: ''}}
        renderScene={this.renderScene}
        configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }} />
    );

},

让我解释一下每个属性的作用:

  • style用于向组件添加样式。
  • initialRoute用于指定导航器要使用的初始路由。 如您所见,我们传递了一个包含name属性的对象,该name属性的值设置为news_items 。 该对象是传递给我们之前定义的renderScene方法的route参数的对象。 这意味着默认情况下,此特定代码将呈现NewsItems组件。
var Component = ROUTES[route.name];

url设置为空字符串,因为默认情况下我们没有要呈现的网页。

  • renderScene负责为特定路线渲染组件。
  • configureScene负责指定在路线之间导航时要使用的动画和手势。 在这种情况下,我们传递了一个返回FloatFromRight动画的函数。 这意味着,当导航到具有较高索引的路线时,新页面会从右向左浮动。 当返回时,它从左到右浮动。 这也增加了向左滑动的手势,作为返回上一条路线的一种方式。
() => { return Navigator.SceneConfigs.FloatFromRight; }

样式是在定义主要组件之后定义的。 我们从StyleSheet对象调用create方法,并传入一个包含样式的对象。 在这种情况下,我们只有一个,它定义它将占据整个屏幕。

var styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

最后,我们注册组件。

AppRegistry.registerComponent('HnReader', () => HnReader);

3. NewsItem组件

NewsItem组件用于呈现新闻项。 定制组件存储在components目录中。 在此目录中,创建news-items.js并向其中添加以下代码:

'use strict';
var React = require('react-native');

var {
  AppRegistry,
  StyleSheet,
  Text,
  ListView,
  View,
  ScrollView,
  TouchableHighlight,
  AsyncStorage
} = React;

var Button = require('react-native-button');
var GiftedSpinner = require('react-native-gifted-spinner');

var api = require('../src/api.js');

var moment = require('moment');

var TOTAL_NEWS_ITEMS = 10;

var NewsItems = React.createClass({

    getInitialState: function() {
        return {
          title: 'HN Reader',
          dataSource: new ListView.DataSource({
            rowHasChanged: (row1, row2) => row1 !== row2,
          }),
          news: {},
          loaded: false
        }    
    },

    render: function() {
        
        return (
            <View style={styles.container}>
                <View style={styles.header}>
                    <View style={styles.header_item}>
                        <Text style={styles.header_text}>{this.state.title}</Text>
                    </View>
                    <View style={styles.header_item}>
                    {  !this.state.loaded && 
                        <GiftedSpinner />
                    }
                    </View>
                </View>
                <View style={styles.body}>
                <ScrollView ref="scrollView">
                {
                    this.state.loaded && 
                    
                    <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews}></ListView>
                    
                }
                </ScrollView>
                </View>
            </View>
        ); 
        
    },

    componentDidMount: function() {
            
        AsyncStorage.getItem('news_items').then((news_items_str) => {

            var news_items = JSON.parse(news_items_str);

            if(news_items != null){
                
                AsyncStorage.getItem('time').then((time_str) => {
                    var time = JSON.parse(time_str);
                    var last_cache = time.last_cache;
                    var current_datetime = moment();

                    var diff_days = current_datetime.diff(last_cache, 'days');
                    
                    if(diff_days > 0){
                        this.getNews();
                    }else{
                        this.updateNewsItemsUI(news_items);
                    }

                });
                

            }else{
                this.getNews();
            }

        }).done();

    },

    renderNews: function(news) {
        return (
            <TouchableHighlight onPress={this.viewPage.bind(this, news.url)} underlayColor={"#E8E8E8"} style={styles.button}>
            <View style={styles.news_item}>
                <Text style={styles.news_item_text}>{news.title}</Text>
            </View>
            </TouchableHighlight>
        );
    },

    viewPage: function(url){
        this.props.navigator.push({name: 'web_page', url: url});
    },

    updateNewsItemsUI: function(news_items){
    
        if(news_items.length == TOTAL_NEWS_ITEMS){

            var ds = this.state.dataSource.cloneWithRows(news_items);
            this.setState({
              'news': ds,
              'loaded': true
            });

        }
        
    },

    updateNewsItemDB: function(news_items){

        if(news_items.length == TOTAL_NEWS_ITEMS){
            AsyncStorage.setItem('news_items', JSON.stringify(news_items));
        }

    },

    getNews: function() {   
        
        var TOP_STORIES_URL = 'https://hacker-news.firebaseio.com/v0/topstories.json';
        var news_items = [];

        AsyncStorage.setItem('time', JSON.stringify({'last_cache': moment()}));

        api(TOP_STORIES_URL).then(
          (top_stories) => {
                
                for(var x = 0; x <= 10; x++){

                    var story_url = "https://hacker-news.firebaseio.com/v0/item/" + top_stories[x] + ".json";

                    api(story_url).then(
                        (story) => {

                            news_items.push(story);
                            this.updateNewsItemsUI(news_items);
                            this.updateNewsItemDB(news_items);

                        }
                    );

                }
                

            }



        );
        
        
    }

});



var styles = StyleSheet.create({
  container: {
    flex: 1
  },
  header: {
    backgroundColor: '#FF6600',
    padding: 10,
    flex: 1,
    justifyContent: 'space-between',
    flexDirection: 'row'
  },
  body: {
    flex: 9,
    backgroundColor: '#F6F6EF'
  },
  header_item: {
    paddingLeft: 10,
    paddingRight: 10,
    justifyContent: 'center'
  },
  header_text: {
    color: '#FFF',
    fontWeight: 'bold',
    fontSize: 15
  },
  button: {
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0'
  },
  news_item: {
    paddingLeft: 10,
    paddingRight: 10,
    paddingTop: 15,
    paddingBottom: 15,
    marginBottom: 5
  },
  news_item_text: {
    color: '#575757',
    fontSize: 18
  }
});

module.exports = NewsItems;

步骤1:导入组件和库

首先,我们导入NewsItem组件所需的组件和库。 我们还创建了一个全局变量,用于存储要缓存的新闻总数。

'use strict';
var React = require('react-native');

var {
  AppRegistry,
  StyleSheet,
  Text,
  ListView,
  View,
  ScrollView,
  TouchableHighlight,
  AsyncStorage
} = React;

var Button = require('react-native-button');
var GiftedSpinner = require('react-native-gifted-spinner');

var api = require('../src/api.js');

var moment = require('moment');

var TOTAL_NEWS_ITEMS = 10;

我们使用了一些以前没有使用过的组件。

  • Text用于在React Native中显示文本。
  • View是创建组件的基本构建块。 可以将其视为网页中的div
  • ListView用于呈现对象数组。
  • ScrollView用于添加滚动条。 React Native不像网页。 当内容大于视图或屏幕时,滚动条不会自动添加。 这就是为什么我们需要使用此组件。
  • TouchableHighlight用于使组件响应触摸事件。
  • AsyncStorage并不是真正的组件。 这是一个用于在React Native中存储本地数据的API。
  • Button是用于创建按钮的第三方组件。
  • 当从网络加载数据时, GiftedSpinner用于创建微调器。
  • api是一个自定义模块,其中包装了fetch ,这是React Native发出网络请求的方式。 要获取网络请求返回的数据,需要大量样板代码,这就是我们将其包装在模块中的原因。 以免在发出网络请求时减少编写代码。
  • moment是用于与时间相关的任何东西的库。

步骤2:创建NewsItems组件

接下来,我们创建NewsItems组件:

var NewsItems = React.createClass({
    ...
});

此组件中有getInitialState函数,该函数用于指定此组件的默认状态。 在React Native中,状态用于存储整个组件中可用的数据。 在这里,我们将存储应用程序的标题, ListView组件的dataSource ,当前news项和一个布尔值loaded ,该布尔值dataSource当前是否正在从网络加载新闻项。 loaded变量用于确定是否显示微调器。 我们将其设置为false因此默认情况下微调器是可见的。

从本地存储或网络加载新闻项目后,将其设置为true以隐藏微调框。 dataSource用于定义要用于ListView组件的数据源的蓝图。 可以将其视为父类,您将定义的每个数据源都将在其中继承。 这要求包含的对象rowHasChanged功能,它告诉ListView到当行已改变重新渲染。

最后, news对象包含ListView数据源的初始值。

getInitialState: function() {
    return {
      title: 'HN Reader',
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      news: {},
      loaded: false
    }    
},

步骤3:实现render功能

render功能渲染此组件的用户界面。 首先,我们将所有内容包装在View 。 然后,在内部有标头和正文。 标头包含标题和微调框。 该主体包含ListView 。 体内的所有内容都包裹在ScrollView以便在内容超出可用空间时自动添加滚动条。

render: function() {
    
    return (
        <View style={styles.container}>
            <View style={styles.header}>
                <View style={styles.header_item}>
                    <Text style={styles.header_text}>{this.state.title}</Text>
                </View>
                <View style={styles.header_item}>
                {  !this.state.loaded && 
                    <GiftedSpinner />
                }
                </View>
            </View>
            <View style={styles.body}>
            <ScrollView ref="scrollView">
            {
                this.state.loaded && 
                
                <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews}></ListView>
                
            }
            </ScrollView>
            </View>
        </View>
    ); 
    
},

标头内部有两个视图:

  • 一个包含标题
  • 一个包含微调器

我们这样做是为了代替直接输出文本和微调器,以便我们可以使用flexbox来控制样式。 稍后,您可以在样式部分中查看如何完成此操作。

我们可以使用this.state来引用存储在状态中的标题,后跟属性名称。 您可能已经注意到,每次我们需要引用一个对象时,都会将其用花括号括起来。 在另一个视图上,我们正在检查状态下的loaded属性是否设置为false ,如果是,则输出微调器。

<View style={styles.header_item}>
    <Text style={styles.header_text}>{this.state.title}</Text>
</View>
<View style={styles.header_item}>
{  !this.state.loaded && 
    <GiftedSpinner />
}
</View>

接下来是身体。

<ScrollView ref="scrollView">
{
    this.state.loaded && 
    
    <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews}></ListView>
    
}
</ScrollView>

注意,我们已经将ref属性传递给ScrollViewref是React Native中的预定义属性,它允许我们为组件分配标识符。 我们可以使用此标识符来引用组件并调用其方法。 这是一个如何工作的示例:

scrollToTop: function(){
    this.refs.scrollView.scrollTo(0);
}

然后,您可以有一个按钮,让它在按下时调用该函数。 这将自动将ScrollView到组件的最上方。

<Button onPress={this.scrollToTop}>scroll to top</Button>

我们不会在应用程序中使用它,但是很高兴知道它的存在。

ScrollView内部,我们检查状态下的loaded属性是否已设置为true 。 如果为true ,则意味着该数据源已经可供ListView使用,我们可以对其进行渲染。

{
    this.state.loaded && 
    
    <ListView initialListSize={1} dataSource={this.state.news} renderRow={this.renderNews}></ListView>
    
}

我们在ListView传递了以下属性:

  • initialListSize用于指定最初安装组件时要呈现的行数。 我们将其设置为1 ,这意味着将花费一帧渲染每一行。 我将其设置为1作为一种性能优化形式,以便用户尽快看到内容。
  • dataSource是要使用的数据源。
  • renderRow是用于呈现列表中每一行的函数。

步骤4:实现componentDidMount函数

接下来,我们有componentDidMount函数,该函数在安装此组件时被调用:

componentDidMount: function() {
        
    AsyncStorage.getItem('news_items').then((news_items_str) => {

        var news_items = JSON.parse(news_items_str);

        if(news_items != null){
            
            AsyncStorage.getItem('time').then((time_str) => {
                var time = JSON.parse(time_str);
                var last_cache = time.last_cache;
                var current_datetime = moment();

                var diff_days = current_datetime.diff(last_cache, 'days');
                
                if(diff_days > 0){
                    this.getNews();
                }else{
                    this.updateNewsItemsUI(news_items);
                }

            });
            

        }else{
            this.getNews();
        }

    }).done();

},

在函数内部,我们尝试获取当前存储在本地存储中的新闻项。 我们使用AsyncStorage API中的getItem方法。 它返回一个promise,因此我们可以通过调用then方法并传递一个函数来访问返回的数据:

AsyncStorage.getItem('news_items').then((news_items_str) => {
    ...
}).done();

AsyncStorage只能存储字符串数据,因此我们使用JSON.parse将JSON字符串转换回JavaScript对象。 如果为null ,则调用getNews方法,该方法从网络中获取数据。

var news_items = JSON.parse(news_items_str);

if(news_items != null){
    ...
}else{
    this.getNews();
}

如果不为空,则使用AsyncStorage来获取新闻项上次存储在本地存储中的时间。 然后,我们将其与当前时间进行比较。 如果相差至少一天(24小时),我们将从网络上获取新闻。 如果不是,我们使用本地存储中的那些。

AsyncStorage.getItem('time').then((time_str) => {
    var time = JSON.parse(time_str);
    var last_cache = time.last_cache; //extract the last cache time
    var current_datetime = moment(); //get the current time
    
    //get the difference in days
    var diff_days = current_datetime.diff(last_cache, 'days');
    
    if(diff_days > 0){
        this.getNews(); //fetch from the network
    }else{
        this.updateNewsItemsUI(news_items); //use the one in the cache
    }

});

步骤5:实现renderNews函数

接下来是渲染列表中每一行的功能。 在ListView前面,我们定义了renderRow属性,其值为this.renderNews 。 这就是那个功能。

迭代中的当前项目作为该函数的参数传递。 这使我们可以访问每个新闻项的titleurl 。 一切都包装在TouchableHighlight组件内,在内部我们输出每个新闻项的标题。

TouchableHighlight组件接受onPress属性,该属性指定用户点击项目时要执行的功能。 在这里,我们调用viewPage函数并将URL绑定到该函数。 underlayColor指定在点击组件时的背景色。

renderNews: function(news) {
    return (
        <TouchableHighlight onPress={this.viewPage.bind(this, news.url)} underlayColor={"#E8E8E8"} style={styles.button}>
        <View style={styles.news_item}>
            <Text style={styles.news_item_text}>{news.title}</Text>
        </View>
        </TouchableHighlight>
    );
},

viewPage函数中,我们保留了之前通过props从index.android.js传递的navigator属性。 在React Native中,道具用于访问从父组件传递的属性。 我们将其称为this.props ,后跟属性名称。

在这里,我们使用this.props.navigator来引用navigator对象。 然后,我们调用push方法将web_page路由以及要由WebPage组件打开的WebPage的URL推web_page导航器。 这使应用程序过渡到WebPage组件。

viewPage: function(url){
    this.props.navigator.push({name: 'web_page', url: url});
},

步骤6:实施updateNewsItemsUI函数

updateNewsItemsUI函数根据作为参数传递的新闻项的数组更新数据源和状态。 仅当news_items的总数等于我们先前为TOTAL_NEWS_ITEMS设置的值时,我们才这样做。 在React Native中,更新状态会触发用户界面重新渲染。 这意味着使用新的数据源调用setState刷新带有新项目的用户界面。

updateNewsItemsUI: function(news_items){
    
    if(news_items.length == TOTAL_NEWS_ITEMS){

        var ds = this.state.dataSource.cloneWithRows(news_items); //update the data source

        //update the state
        this.setState({
          'news': ds,
          'loaded': true
        });

    }
    
},

步骤7:更新本地存储

updateNewsItemDB函数更新存储在本地存储中的新闻项。 我们使用JSON.stringify函数将数组转换为JSON字符串,以便我们可以使用AsyncStorage进行存储。

updateNewsItemDB: function(news_items){

    if(news_items.length == TOTAL_NEWS_ITEMS){
        AsyncStorage.setItem('news_items', JSON.stringify(news_items));
    }

},

步骤8:提取新闻项

getNews函数更新存储最后一次缓存数据的本地存储项目,从Hacker News API获取新闻项目,根据获取的新项目更新用户界面和本地存储。

getNews: function() {   
    
    var TOP_STORIES_URL = 'https://hacker-news.firebaseio.com/v0/topstories.json';
    var news_items = [];

    AsyncStorage.setItem('time', JSON.stringify({'last_cache': moment()}));

    api(TOP_STORIES_URL).then(
      (top_stories) => {
            
            for(var x = 0; x <= 10; x++){

                var story_url = "https://hacker-news.firebaseio.com/v0/item/" + top_stories[x] + ".json";

                api(story_url).then(
                    (story) => {

                        news_items.push(story);
                        this.updateNewsItemsUI(news_items);
                        this.updateNewsItemDB(news_items);

                    }
                );

            }
        }
    );
    
}

Hacker News API中的热门新闻资源返回一个数组,如下所示:

[ 10977819, 10977786, 10977295, 10978322, 10976737, 10978069, 10974929, 10975813, 10974552, 10978077, 10978306, 10973956, 10975838, 10974870...

这些是在Hacker News上发布的热门文章的标识符。 这就是为什么我们需要遍历此数组并对每个项目进行网络请求,以获取实际的详细信息,例如标题和URL。

然后,将其推送到news_items数组,并调用updateNewsItemsUIupdateNewsItemDB函数以更新用户界面和本地存储。

for(var x = 0; x <= 10; x++){

    var story_url = "https://hacker-news.firebaseio.com/v0/item/" + top_stories[x] + ".json";

    api(story_url).then(
        (story) => {

            news_items.push(story);
            this.updateNewsItemsUI(news_items);
            this.updateNewsItemDB(news_items);

        }
    );

}

步骤9:样式

添加以下样式:

var styles = StyleSheet.create({
  container: {
    flex: 1
  },
  header: {
    backgroundColor: '#FF6600',
    padding: 10,
    flex: 1,
    justifyContent: 'space-between',
    flexDirection: 'row'
  },
  body: {
    flex: 9,
    backgroundColor: '#F6F6EF'
  },
  header_item: {
    paddingLeft: 10,
    paddingRight: 10,
    justifyContent: 'center'
  },
  header_text: {
    color: '#FFF',
    fontWeight: 'bold',
    fontSize: 15
  },
  button: {
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0'
  },
  news_item: {
    paddingLeft: 10,
    paddingRight: 10,
    paddingTop: 15,
    paddingBottom: 15,
    marginBottom: 5
  },
  news_item_text: {
    color: '#575757',
    fontSize: 18
  }
});

其中大多数是标准CSS,但请注意,我们已将驼峰替换为驼峰式语法。 这不是因为如果使用诸如padding-left类的语法,就会出现语法错误。 这是因为React Native需要它。 另请注意, 并非所有css属性都可以使用

就是说,这里有一些声明可能不是那么直观,特别是如果您以前没有使用过flexbox的话

container: {
    flex: 1
},
header: {
    backgroundColor: '#FF6600',
    padding: 10,
    flex: 1,
    justifyContent: 'space-between',
    flexDirection: 'row'
},
body: {
    flex: 9,
    backgroundColor: '#F6F6EF'
},

这是NewsItems组件的标记的简化版本,以帮助您可视化它:

<View style={styles.container}>
    <View style={styles.header}>
        ...
    </View>
    <View style={styles.body}>
        ...
    </View>
</View>

我们将container设置为flex: 1 ,这意味着它占据了整个屏幕。 在container内,我们有headerbody ,分别将其设置为flex: 1flex: 9 。 在这种情况下,因为header具有同级,所以flex: 1不会占据整个屏幕。 这两个将共享整个屏幕。 这意味着由于我们拥有flex: 1flex: 9 ,所以整个屏幕将被分为十个部分。 每个兄弟姐妹的flex值相加。

header占据屏幕的10%, body占据屏幕的90%。 基本思想是选择一个代表整个屏幕的高度或宽度的数字,然后每个兄弟姐妹从该数字中取一个片段。 但是,不要为此过度。 您不想使用1000,除非您想在电影院中部署应用程序。 当找到高度时,我发现十是神奇的数字。

对于header我们设置了以下样式:

header: {
    backgroundColor: '#FF6600',
    padding: 10,
    flex: 1,
    justifyContent: 'space-between',
    flexDirection: 'row'
},

为了刷新您的内存,以下是标头内的简化标记:

<View style={styles.header_item}>
    ...
</View>
<View style={styles.header_item}>
    ...
</View>

样式添加到其中:

header_item: {
    paddingLeft: 10,
    paddingRight: 10,
    justifyContent: 'center'
},

我们将flexDirection设置为row ,将justifyContent设置为其父级( header space-between 。 这意味着它将均匀分布其子项,第一个子项在行的开头,最后一个子项在行的末尾。

默认情况下, flexDirection设置为column ,这意味着由于移动是水平的,因此每个子项都占据整行。 使用row将使流垂直,以便每个孩子并排。 如果您仍然对Flexbox感到困惑,或者想了解更多有关Flexbox的信息,请查看CSS:Flexbox Essentials

最后,将组件暴露给外界:

module.exports = NewsItems;

结论

在这一点上,您应该对如何以React Native方式进行操作有个好主意。 具体来说,您已经学习了如何创建新的React Native项目,通过npm安装第三方库,使用各种组件以及向应用程序添加样式。

在下一篇文章中,我们将继续将WebPage组件添加到News Reader应用程序中。 随时在下面的评论部分中留下任何问题或评论。