從一個React Native Demo開始(內附Demo源碼)

先看democss

寫在最前面

從開始學RN到如今大概有2個星期天左右了,這裏先記錄一下,也算個小階段總結。就目前感受,RN的優點和劣勢都很明顯;html

  • 優點
    • RN是混合開發一份代碼多端使用
    • 代碼與前端類似,Web轉RN比較輕鬆
  • 劣勢
    • RN組件由多個第三方維護,更新不可控,會有停更不兼容的風險
    • 會因爲RN版本,組件版本,Xcode版本的不一樣而隨機組合成各類坑(這點很使人煩躁,大部分時間都浪費在這)
    • 多端兼容適配成本大,並且會隨着項目規模而增大難度,到必定程度開發成本會比原生的高,如Airbnb宣佈放棄使用RN,迴歸原生技術

正文

一 環境安裝

官網說的很詳細,按照官網的步驟基本沒問題,就很少贅述前端

官網地址: reactnative.cn/docs/gettin…node

二 熟悉RN

建立Q項目,並用iOS模擬器運行起來react

react-native init q
 cd q
 react-native run-ios
複製代碼

項目內容以下:android

  • android:Android工程文件
  • ios: iOS工程文件
  • node_modules: React-native組件依賴存放的文件夾
  • package.json: 依賴配置json, 相似於cocoPods中的「Podfile」
  • index.js: 項目入口 ...

先看index.js, import 是引入文件,AppRegistry.registerComponent(appName, () => App);整個的意思就是將工程目錄的App.js註冊成組件並引入,因此一開始顯示的即App.js裏面的內容ios

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

複製代碼

App.js文件裏面大體能夠分紅三部分web

  • 引入組件
  • 搭建UI
  • 樣式

有過前端開發經驗的朋友對ViewTextScrollView這些並不陌生,在React-native中,全部組件都要單獨引入,全部組件及做用可看官方文檔npm

import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
} from 'react-native';

import {
  Header,
  LearnMoreLinks,
  Colors,
  DebugInstructions,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

複製代碼

這裏部份內容是畫UI,基本上和html沒差多少,都是用各類組件的堆砌。學過web或者小程序之類的看起來會很簡單,沒學過的話,建議選去學學最基本的html + cssjson

const App: () => React$Node = () => {
  return (
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
        .....
        .....
         </SafeAreaView>
    </>
  );
};
export default App;
複製代碼

這裏是各類樣式,大部分都是沿用css的,看到這裏大概感受到React-native其實就是前段代碼換個殼,對於有前段知識的人學起來應該會很輕鬆,沒有相關知識的話建議仍是先去學學基礎的再來搞React-native

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
   .....
   .....
});
複製代碼

三 嘗試Demo

有這些瞭解後,能夠試着作一個簡單的列表頁面

1.導入組件,須要的組件大概有這些,重點是FlatList列表組件

import React, { Component } from "react";

import { Image, FlatList, StyleSheet, Text, View } from "react-native";
複製代碼

2.導出默認類App擴展組件,包括住下面的其餘代碼

export default class App extends Component {
}
複製代碼

3.建立個長度是8的data數組,後面可給FlatList賦值用

constructor(props) {
    super(props);
    this.state = {
      data: [{}, {}, {}, {}, {}, {}, {}, {}],
    };
}
複製代碼

4.RN的render()函數實際上跟iOS的ViewDidLoad()函數類似,返回的就是具體的內容,內容很固定,只能是一個組件,這裏我返回的是FlatList,字段說明以下

  • data:須要給予一個數組,數組長度與列表item對應
  • style:列表樣式
  • renderItem:Item渲染,這裏是直接調用renderMovie渲染
  • keyExtractor:設置每一個item惟一的key,相似於索引
render() {
    return (
      <FlatList
        data={this.state.data}
        style={styles.list}
        renderItem={this.renderMovie.bind(this)}  
        keyExtractor={item => item.id}
      />
    );
 }
複製代碼

5.經過renderMovie函數返回item的內容,這裏能夠任意發揮

renderMovie({ item }) {
    return (
        <View style={styles.container}>
          <Image
            source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
            style={styles.thumbnail}
          />
          <View style={styles.rightContainer}>
            <View style={styles.titleWithout}>
              <Text style={styles.title}>羅小黑戰記</Text>
              <Text style={styles.tip}>中國巨屏</Text>
            </View>
            <Text style={styles.score}>貓眼評分<Text style={styles.grade}> 9.8 </Text></Text>
            <Text style={styles.starring}>主演:羅小黑,羅小白</Text>
            <Text style={styles.cinema}>今天129加音樂反映124場</Text>
          </View>
          <Text style={styles.buy}>購買</Text>
        </View>
    );
  } 
複製代碼

6.最後是樣式,其實這些隨意發揮便可

這樣簡單的一個頁面就完成了,完整代碼以下,能夠直接copy替代原有內容運行便可看到效果

import React, { Component } from "react";

import { Image, FlatList, StyleSheet, Text, View, TouchableOpacity } from "react-native";


export default class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
    };
  }

  render() {
    return (
      <FlatList
        data={this.state.data}
        style={styles.list}
        renderItem={this.renderMovie.bind(this)}  
        keyExtractor={item => item.id}
      />
    );
  }

  renderMovie({ item }) {
    return (
        <View style={styles.container}>
          <Image
            source={{ uri: 'https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=e4d6ea2325dda3cc0be4bf2639d25e3c/b64543a98226cffcb1f7cc0eb2014a90f703eaa9.jpg' }}
            style={styles.thumbnail}
          />
          <View style={styles.rightContainer}>
            <View style={styles.titleWithout}>
              <Text style={styles.title}>羅小黑戰記</Text>
              <Text style={styles.tip}>中國巨屏</Text>
            </View>
            <Text style={styles.score}>貓眼評分<Text style={styles.grade}> 9.8 </Text></Text>
            <Text style={styles.starring}>主演:羅小黑,羅小白</Text>
            <Text style={styles.cinema}>今天129加音樂反映124場</Text>
          </View>
          <Text style={styles.buy}>購買</Text>
        </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#fff"
  },
  header: {
    height: 20,
    marginTop: 44,
  },
  rightContainer: {
    flex: 1,
    paddingLeft: 18,
  },
  titleWithout: {
    flexDirection: "row",
    fontWeight: "bold",
    alignItems: "center",
  },
  title: {
    fontSize: 14,
    marginTop: 4,
    lineHeight: 0,
  },
  tip: {
    backgroundColor: "#999",
    fontSize: 8,
    textAlign: "center",
    color: "#fff",
    height: 14,
    width: 40,
    lineHeight: 14,
    borderRadius: 2,
    marginLeft: 4,
    marginTop: 4,
  },
  score: {
    paddingTop: 8,
    fontSize: 12,
    color: "#666",
  },
  starring: {
    paddingTop: 4,
    fontSize: 12,
    color: "#666",
  },
  cinema: {
    paddingTop: 4,
    fontSize: 12,
    color: "#666",
  },
  buy: {
    fontSize: 12,
    // color:'#333',
    width: 46,
    height: 24,
    lineHeight: 24,
    textAlign: "center",
    backgroundColor: "#D44145",
    color: "#fff",
    borderRadius: 12,
    marginRight: 20,

  },
  grade: {
    color: "#DF8D7A",
  },
  year: {
    textAlign: "center"
  },
  thumbnail: {
    width: 68,
    height: 94,
    marginLeft: 20,
    marginTop: 10,
    marginBottom: 10,

  },
  list: {
    paddingTop:40,
    backgroundColor: "#F5FCFF"
  },
  headerOutline: {
    backgroundColor: "#fff",
    marginTop: 44,
  },
  headerInside: {
    backgroundColor: "#f5f5f5",
    flexDirection: "row",
    justifyContent: 'space-between',
    marginLeft: 20,
    marginRight: 20,
    marginBottom: 6,
    paddingTop: 10,
    paddingBottom: 4,
    paddingLeft: 10,
    paddingRight: 10,
  },
  trendIcon: {
    width: 10,
    height: 14,
    marginLeft: 10,
    marginTop: -1,
  },
  trendText: {
    height: 22,
    color: '#333',
    fontWeight: "bold",
  },
  trendR: {
    color: '#333',
    fontSize: 10,
    fontWeight: "bold",
    height: 22,
  },
  trendRText: {

  },
  trendMoney: {
    color: '#D24349',
  },
});
複製代碼

二 Navigation與Tabbar

如圖,最終目的是建立一個帶Navigation,Tabbar的demo,可分爲三個步驟

  1. 安裝組件
  2. 建立tabbar上的兩個跟根頁面和一個詳情頁面
  3. 修改index.js入口

1.先安裝react-navigation組件

注:這裏有個天坑,react-navigation4與3差距很大,如今網上的教程基本使用的都是react-navigation 3,這裏我也是兜兜轉轉搞了許久才意識到的,你們都是初學者,建議安裝版本3

yarn add react-navigation@3.5.1
yarn add react-native-gesture-handler

複製代碼

這裏可能會報這個錯

Error: Requiring unknown module "447". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`.

複製代碼

這個錯緣由不少,能夠嘗試

npm install
react-native run-ios
複製代碼

或者

cd ios
pod install
cd ..
react-native run-ios
複製代碼

2.建立detailsScreen.js,settingScreen.js,navigation.js文件

detailsScreen.js內容

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

export default class detailsScreen extends React.Component {

   
    render() {
        return (
            <View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>詳情頁</Text></View>
        );
    }
}
複製代碼

settingScreen.js內容

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

export default class settingScreen extends React.Component {

   
    render() {
        return (
            <View style={{flex:1, alignItems:'center',justifyContent: 'center'}}><Text>設置頁</Text></View>

        );
    }
}
複製代碼

navigation.js內容須要分步講解一下,首先引入全部須要的組件與頁面

import React from 'react';
import { Text } from 'react-native'

import HomeScreen from "./App";        
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";

import {
    createStackNavigator,
    createAppContainer,
    createBottomTabNavigator
} from 'react-navigation';

複製代碼

這裏是聲明HomeStack,SettingsStack兩個組件;

createStackNavigator 提供APP屏幕之間切換的能力,它是以棧的形式還管理屏幕之間的切換,新切換到的屏幕會放在棧的頂部。

const HomeStack = createStackNavigator({
    Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
    Settings: { screen: SettingScreen },
})

複製代碼

這裏聲明TabNavigator

createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig)至關於iOS裏面的TabBarController,屏幕下方的標籤欄

  • RouteConfigs(必選):路由配置對象是從路由名稱到路由配置的映射,告訴導航器該路由呈現什麼。
  • BottomTabNavigatorConfig(可選):配置導航器的路由(如:默認首屏,navigationOptions,paths等)樣式(如,轉場模式mode、頭部模式等)。
const TabNavigator = createBottomTabNavigator(
    {
      Home: { screen: HomeStack },
      Settings: { screen: SettingsStack }
    },
    {
      navigationOptions: () => ({
        header: null
      }),
      defaultNavigationOptions: ({ navigation }) => ({
        tabBarLabel: ({ tintColor}) => {
          const { routeName } = navigation.state
          switch (routeName) {
            case 'Home':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'首頁'}</Text>
            case 'Settings':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'設置'}</Text>
          }
        },
        tabBarIcon: ({ focused, tintColor }) => {
            let urld 
            const { routeName } = navigation.state
            switch (routeName) {
                case 'Home':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>    
                case 'Settings':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>    
            }
        }
      }),
      tabBarOptions: {
        inactiveTintColor: 'gray',
      }
    }
)

複製代碼

最後設置路由並返回

const AppStack = createStackNavigator({
    Tabs: TabNavigator,
    Details: { screen: DetailsScreen },
  }, {
    defaultNavigationOptions: () => ({
    })
  })
export default createAppContainer(AppStack)

複製代碼

完整代碼以下

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

import HomeScreen from "./App";     
import DetailsScreen from "./detailScreen";
import SettingScreen from "./settingScreen";

import {
    createStackNavigator,
    createAppContainer,
    createBottomTabNavigator
} from 'react-navigation';

const HomeStack = createStackNavigator({
    Home: { screen: HomeScreen, }
})
const SettingsStack = createStackNavigator({
    Settings: { screen: SettingScreen },
})

const TabNavigator = createBottomTabNavigator(
    {
      Home: { screen: HomeStack },
      Settings: { screen: SettingsStack }
    },
    {
      navigationOptions: () => ({
        header: null
      }),
      defaultNavigationOptions: ({ navigation }) => ({
        tabBarLabel: ({ tintColor}) => {
          const { routeName } = navigation.state
          switch (routeName) {
            case 'Home':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'首頁'}</Text>
            case 'Settings':
              return <Text style={{ color: tintColor, fontSize: 12 }}>{'設置'}</Text>
          }
        },
        tabBarIcon: ({ focused, tintColor }) => {
            let urld 
            const { routeName } = navigation.state
            switch (routeName) {
                case 'Home':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/119/1191814.gif' }} style={[{height: 20, width: 20}]}/>    
                case 'Settings':
                    return <Image source={{ uri: 'https://static.easyicon.net/preview/121/1215319.gif' }} style={[{height: 20, width: 20}]}/>    
            }
        }
      }),
      tabBarOptions: {
        inactiveTintColor: 'gray',
      }
    }
)

const AppStack = createStackNavigator({
    Tabs: TabNavigator,
    Details:DetailsScreen,
  }, {
    defaultNavigationOptions: () => ({
    })
  })


export default createAppContainer(AppStack)


複製代碼

3.修改index.js入口

這裏僅僅只是把入口改成navigation.js

import {AppRegistry} from 'react-native';
import Nav from './navigation.js';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => Nav);

複製代碼

保存基本就能看到App的架子大概造成了

接下來要設置一下點擊事件,讓demo能夠跳轉

先回到App.js頁面 設置首頁導航欄標題

static navigationOptions = ({ navigation }) => {
    const { params = {} } = navigation.state
    const onPressRightButtonFunc = params.openPublisher || function () { }
    return {
      title: '首頁',
    }
}
複製代碼

引入TouchableOpacity設置點擊事件

import {  TouchableOpacity } from "react-native";
 
 ...
 ...
 renderMovie({ item }) {
    const navigate = this.props.navigation;
    return (
      <TouchableOpacity activeOpacity={0.5} onPress={() => navigate.navigate('Details')} > //'Details'是以前在navigation.js聲明好的了
      ... //這裏是以前item的UI代碼
      </TouchableOpacity>

複製代碼

到這裏基本已經完成了這個demo,其餘的都是一些重複的UI工做也不贅述了,這是稍微優化過的代碼和詳情頁,看不懂的能夠根據根據這源碼來。

這裏個人源碼是將基本組件都下好,下載運行便可,由於比較大先上傳到百度雲。

連接: pan.baidu.com/s/1854tyx1R… 提取碼: kgmb

網上的其餘demo對新人都很不友好,須要安裝各個組件再運行起來,各類報錯容易勸退新人

後記

初衷是想讓新手快速的入門製做一個demo,後面發現仍是須要一點web經驗的,內容有點多,說得沒那麼細緻的地方請見諒。後續會一直持續更新這個demo;

若是以爲對這篇文章對您有一點幫助的話,歡迎關注,戳這裏 → 蘆葦科技

相關文章
相關標籤/搜索