Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Native Begineer Tutorial

Avon
August 18, 2018

React Native Begineer Tutorial

React Native 入門實作課程 ppt

Avon

August 18, 2018
Tweet

Other Decks in Programming

Transcript

  1. 成功⼤大學資源⼯工程學系 清華⼤大學動⼒力力機械研究所 ⾃自學程式 女⼈人迷 Hackathon冠軍-智慧法律律APP 台科X政⼤大 Hackathon亞軍-物聯聯網鋼琴 台⼤大 Hackathon企業獎及⼈人氣獎-fb尋找⼿手機/⼈人臉辨識 APP

    Girls 創辦⼈人 印氪股份有限公司接案iOS軟體⼯工程師 達暉資訊科技有限公司iOS軟體⼯工程師 Uitox象網智慧科技有限公司APP軟體⼯工程師 天堂遊戲有限公司APP軟體⼯工程師 雅⽅方 Avon
  2. APP Girls APP Girls aims to give tools for girls

    and women to build their ideas. 希望不斷推廣⽽而有更更多女⽣生⼀一同透過交流學習,⼀一起 了了解程式開發並能互相切磋,歡迎對程式語⾔言有興趣 的⼀一起加入!
  3. ⼤大綱 react native 介紹 react native 基本使⽤用範例例 利利⽤用react-native 快速建立react native專案

    執⾏行行APP到iOS&Android雙平台上 VSCode常⽤用功能介紹 專案架構講解 元件基本架構規則 必須熟悉語法寶典 state介紹 專案時間: 利利⽤用react native做出⼀一個密碼判斷 State v.s. Props ⾃自製元件 react-navigation教學 專案時間: 套⽤用react-navigation做各種換⾴頁 專案時間: 套⽤用react-navigation並⾃自製data列列表 做Notice Cart Storage儲存⼯工具 redux數據流框架 Storage v.s. redux ⼩小練習時間 API介紹 PostMan⼯工具 串串接API教學 專案時間: Notice Cart + API 專案實作:我的收藏 Part1 初探react native 熟悉語法架構 Part2 加強必備戰⼒力力 熟悉套件&數據流 Part3 串串接後台不可或缺 熟悉串串接API與實作
  4. 2015 年年 3 ⽉月份, Facebook 開放了了 React Native 的原始碼,這是 ⼀一個能夠讓你利利⽤用

    JavaScript 建立原⽣生 iOS 及 Android App 的框 架,它已經在世界各地流⾏行行的移動App中廣泛應⽤用,例例如Facebook, Bloomberg, Skype, Instagram和Uber eat。 React Native 「只需學習⼀一次,到處皆可實作」( Learn Once, Write Anywhere ) 學好⼀一套技能,然後運⽤用這套技能來來建構多種平台的 App
  5. React Native 特點 PhoneGap 之類的框架 將網⾴頁內容包裝成 WebView ,形成 UI 元素,看起來來其實跟原⽣生的

    有些不⼀一樣 —>基於HTML, CSS,直接使⽤用HTML渲染,⼤大量量使⽤用DOM,很耗性能 React Native 使⽤用 iOS 或 Android 原⽣生⽀支援的 JavaScript 元件,因此打造出來來的 App 就跟原⽣生的沒有兩兩樣。 ex: 代替web組件’div’和’span' —> 基本上就是⽤用原⽣生組件’View’和’Text' —>基於javascript 即時編譯,運⾏行行效率比較⾼高,⽽而且此框架⽤用 Virtual DOM渲染
  6. 利利⽤用 react-native 快速建立 react native 專案 npm install -g react-native-cli

    (cli = command line interface) 開專案: react-native init AwesomeProject cd AwesomeProject For iOS: react-native run-ios (或是打開資料夾內ios/AwesomeProject.xcodeproj,並點擊Run按鈕。) Tips: 每次修改好程式碼,只要 Cmd+R 就可以reload 除錯⼯工具:’Cmd+D' or shake for dev menu,就會有menu選單跑出來來
  7. 利利⽤用 react-native 快速建立 react native 專案 For Android: A. 環境參參數設置

    (~/ =>就是路路徑 /Users/使⽤用者名稱/) 在終端下使⽤用vi ~/.bash_profile (命令創建或編輯 (按o 代表換⾏行行模式 打好第⼆二⾏行行後 esc 跳出後 再:wq 儲存離開)) export ANDROID_HOME=~/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools 執⾏行行source ~/.bash_profile B. 要執⾏行行以前 1. 先開啟android studio 2. 點選“Open an existing Android Studio project”,打開專案內的android資料夾 3. 法⼀一:先打開模擬器,然後終端機react-native run-android 法⼆二: 1. npm start 2. 打開android studio內的斧頭 3. 按⼀一下三⾓角形run (會⾃自動請你打開模擬器) Tips: 按兩兩下R鍵,就可以重新整理理模擬器 command+m,就可以跑出選單(和iOS是 command + d 是⼀一樣的意思) 就可以選擇 Debug JS Remotely http://localhost:8081/debugger-ui/
  8. android 原⽣生程式碼 ios 原⽣生程式碼 此App的進入點,也就是開始執⾏行行的地⽅方 整個專案的路路由在這裏匯入 專案依賴管理理檔案 安裝⽬目錄,套件放置區 專案說明,主要給原⽣生 app

    打包⽤用,包括專案名稱和⼿手機桌⾯面展⽰示名稱 babel的配置檔案,React Native預設使⽤用babel編譯JavaScript程式碼 以前舊版建立專案時,會⾃自動⽣生成 index.ios.js & index.android.js 但新版只有index.js,並把App.js匯入 到index.js
  9. import 可以使物件屬性在整個檔案的範圍內都可以被參參照到 export 匯出,讓別個檔案使⽤用 stylesheet ⽤用於視圖內容的樣式,主要使⽤用 CSS(Cascading Style Sheets,串串接樣式表)來來設定 App

    的 UI 樣式 src: source code 通常會⾃自⾏行行建立 src ⼤大資料夾 (作為App應⽤用Javascript部分所有程式碼和資源的根⽬目錄,放置所有原始的 react native 程式碼) 補充
  10. 建構component組件基本三步驟 1. import 模組, 套件 2. 建立樣式表(StyleSheet) 3. class建構版⾯面樣式 import

    React, { Component } from 'react'; export default class App extends Component { state = { name: '', validCode: '', clickButton: false } changeName = () => { // this.setState({ name: 'avon' }) this.state.name === 'avon' ? this.setState({ name: '歡迎回來來' }) : this.setState({ name: 'avon' }) } render() { return ( <View style={styles.container}> <Text style={styles.welcome}>Welcome to React Native!</Text> <Text style={styles.instructions}>Hello! {this.state.name}</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } }); 初始化state function執⾏行行 渲染
  11. import { StyleSheet, } from 'react-native'; var styles = StyleSheet.create({

    container: { paddingTop: 10, backgroundColor: '#b5b5b5', justifyContent: 'center', alignItems: 'center', height: 100 }, red: { color: 'red', }, blue: { fontWeight: 'bold', fontSize: 60, color: ‘blue' }, }); … <View style={styles.container}> <Text style={styles.blue} > hello </Text> <Text style={[styles.blue, styles.red]}> Good Luck </Text> </View > // StyleSheet.create來來集中定義組件的樣式 // CSS中的“層疊”做法 樣式(StyleSheet),多個樣式管理理
  12. ✦ 新增⼀一個state 在渲染的JSX內使⽤用{this.state.xxx}來來讀取 ✦ 新增⼀一個funtion 把剛才按鈕內使⽤用的this.setState({xxx}) ⽤用此function包起來來 ✦ 新增⼀一個按鈕來來觸發動作 裡⾯面使⽤用this.setState({xxx})來來改變值

    p.s. 補充: 三元運算判斷 <Button title='Click Me' onPress={( ) => this.setState({ name: 'baby' })}/> changeName = ( ) => { } <Button title='Click Me' onPress={( ) => this.changeName( )}/> state初成熟...
  13. ✓ state語法 初始化: state = { name:' ', age:18} 讀取:

    {this.state.name} 存入: this.setState({ name: ‘avon' }) ✓ function 不帶變數: aaa = ( ) => { } 套⽤用: ( )=>this.aaa( ) 帶變數: aaa = (xxx) => { } 套⽤用: (arg) => this.aaa(arg) ✓ tag內加入參參數 駝峰式⼩小寫 ex: ✓ style寫法 在直接寫在tag內 寫在styleSheet內再引⽤用到tag內 ✓ command標註 // or /* */ or 在render內{/* */} ✓ 元件⼀一定要⼤大寫(⾃自製元件也⼀一樣) <Text style={styles.welcome}>成功</Text> <Text style={{ color: 'green' }}>成功</Text> const styles = StyleSheet.create({ welcome: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } }); <Button title='Click Me' onPress={( ) => this.setState({ name: 'baby' })}/> 必須熟悉的常⽤用⽤用法寶典
  14. export default class App extends Component { state = {

    validCode: '', clickButton: false } getNumber = () => { if (this.state.validCode === '1234') { return ( <Text style={{ color: 'green' }}>輸入成功</Text>) } else { return (<Text style={{ color: 'orange' }}>請輸入密碼</Text>) } } render() { return ( <View style={styles.container}> {/* 輸入⽂文字 */} <TextInput maxLength={4} style={{ height: 50, width: 300, borderRadius: 0, borderColor: 'darkgray', backgroundColor: 'white', fontSize: 28, textAlign: 'center' }} onChangeText={(text) => this.setState({ validCode: text })} keyboardType={"numeric"} value={this.state.validCode} /> {/* 法⼀一:輸入⽂文字,即時判斷 */} {this.state.validCode === '1234' ? <Text style={{ color: 'blue' }}>輸入成功</Text> : <Text style={{ color: 'red' }}>請輸入密碼</Text>} {/* 法⼆二:輸入⽂文字,即時判斷,⽤用function包起來來 */} {this.getNumber()} </View > ); } } 專案實作:密碼判斷 practice project
  15. 新增元件 MyButton.js (⼦子) Import 需要的元件 render ⾃自製button export ⾃自製button import

    { TouchableOpacity, Text,} from 'react-native'; class MyButton extends Component { render() { return ( <TouchableOpacity onPress={this.props.onPress} > <Text>{this.props.title}</Text> </TouchableOpacity> ) } } <MyButton title={'Click'} onPress={()=>this.changeName()} /> App.js(⽗父) export default MyButton import MyButton from ‘./MyButton'
  16. 補充:⾃自製元件-屬性 Prop types import PropTypes from 'prop-types'; MyButton.propTypes = {

    title: PropTypes.string.isRequired, color: PropTypes.string, backgroundColor: PropTypes.string, borderRadius: PropTypes.number, onPress: PropTypes.func, }; MyButton.defaultProps = { color: 'white', backgroundColor: 'black', onPress: null, borderRadius: 5, };
  17. 專案實作:元件套件換⾴頁 practice project tab換⾴頁: 參參考https://reactnavigation.org/docs/en/tab-based-navigation.html 在程式碼最上⽅方“引入套件” import { createBottomTabNavigator, }

    from 'react-navigation'; nav bar換⾴頁: 參參考https://reactnavigation.org/docs/en/stack-navigator.html#docsNav 在程式碼最上⽅方“引入套件” import { createStackNavigator, } from 'react-navigation'; 學習換⾴頁-套⽤用套件React-navigation 安裝:npm install --save react-navigation
  18. Tab換⾴頁 const App = createBottomTabNavigator( { Home: { screen: HomeScreen

    }, Profile: { screen: ProfileScreen }, }, // add custom bottom tabs { navigationOptions: ({ navigation }) => ({ tabBarIcon: ({ tintColor }) => { const { routeName } = navigation.state; let iconName; if (routeName === 'Home') { iconName = 'ios-trophy' } else if (routeName === 'Profile') { iconName = 'ios-options' } return <Ionicons name={iconName} size={25} color={tintColor} /> }, }), tabBarOptions: { activeTintColor: 'tomato', inactiveTintColor: 'gray', }, } ); export default App 設定客製化參參數 import HomeScreen from './src/screens/HomeScreen' import ProfileScreen from './src/screens/ProfileScreen' import需要有Tab的⾴頁⾯面 ⽤用createBottomTabNavigator包起來來
  19. Nav bar換⾴頁 ⽤用createStackNavigator包起來來 設定客製化參參數 import HomeMainScreen from './HomeMainScreen' import HomeDetailScreen

    from './HomeDetailScreen' const HomeScreen = createStackNavigator( { HomeMain: { screen: HomeMainScreen, navigationOptions: () => ({ title: '這是上⽅方title', headerBackTitle: '返回', }), }, HomeDetail: { screen: HomeDetailScreen }, } ); import需要有Tab的⾴頁⾯面 export default HomeScreen
  20. const HomeStack = createStackNavigator({ Home: { screen: HomeScreen, navigationOptions: ()

    => ({ title: 'Home title', headerBackTitle: '返回', }), }, HomeDetail: { screen: HomeDetailScreen } }); const ProfileStack = createStackNavigator({ Profile: { screen: ProfileScreen, navigationOptions: () => ({ title: 'Profile title', headerBackTitle: '返回', }), }, HomeDetail: { screen: HomeDetailScreen } }); const App = createBottomTabNavigator( { Home: HomeStack, Profile: ProfileStack, }, // add custom bottom tabs … App.js
  21. this.props.navigation.navigate('HomeDetail', { name: ‘avon' }) render() { const name =

    this.props.navigation.getParam('name', 'default value'); return ( … <Text>{name}</Text> … ); } HomeScreen.js HomeDetailScreen.js 換⾴頁+傳變數給內⾴頁 內⾴頁拿到變數呈現
  22. this.props.navigation.navigate('HomeDetail', { name: ‘avon’, function: arg=> this.changeFood(arg) }) render() {

    return ( … <Button title="Get Food" onPress={() => this.props.navigation.getParam(‘function')('apple')} /> … ); } HomeScreen.js HomeDetailScreen.js 傳function給內⾴頁,等內⾴頁拿變數傳回來來 內⾴頁拿到function並且回傳參參數 changeFood = (foodGet) => { this.setState({ food: foodGet }) }
  23. Data var FAKE_DATA = [ { notice: '恭喜您!達成環島100次', date: '2018/08/01

    14:00', }, { notice: '您的會員⾝身份認證,已審核通過!', date: '2018/08/02 12:00', }, { notice: '撥款通知:本公司已將款項$123456撥入您的指定銀⾏行行帳⼾戶.', date: '2018/08/05 12:30', } … ]
  24. 列列表 ListView state = { dataSource: new ListView.DataSource({ rowHasChanged: (row1,

    row2) => row1 !== row2 }) } componentDidMount() { var books = FAKE_DATA this.setState({ dataSource: this.state.dataSource.cloneWithRows(books) }) } render() { return ( <View> <ListView dataSource={this.state.dataSource} renderRow={arg => this.renderBook(arg)} style={styles.listView} /> </View> ) }
  25. renderBook = (cases) => { return ( <TouchableHighlight onPress={() =>

    this.showNoticeDetail(cases)}> <View> <View style={styles.MainView}> <Image source={{ uri: 'https://pic.pimg.tw/cc1895/1431185098-1614666523.jpg' }} style={styles.thumbnail} /> <View style={{ flex: 1 }}> <Text ellipsizeMode='tail' numberOfLines={3} style={{ color: 'black', fontSize: 15, marginTop: 8 }} >{cases.notice}</Text> <Text ellipsizeMode='tail' numberOfLines={3} style={{ marginTop: 8, fontSize: 13, marginBottom: 8 }} >{cases.date}</Text> </View> <Image style={styles.image} source={require('../img/ic_arrow_right.png')} /> </View> <View style={styles.separator} /> </View> </TouchableHighlight> ) } 列列表 ListView
  26. showNoticeDetail(cases) { this.props.navigation.navigate('HomeDetail', { passProps: cases, }) } 傳遞資料 const

    passProps = this.props.navigation.getParam('passProps', 'some default value'); … <Text>{passProps.notice}</Text> …
  27. AsyncStorage 結合 async await AsyncStorage: 簡單的、異異步的、持久化的Key-Value存儲系統 —> 處理理異異步(非同步)的救星: async(非同步) await(等待)

    等待這段函式完成後,在往下繼續執⾏行行 備註:async await 也常⽤用來來解決callback hell的問題,讓程式碼易易讀性更更⾼高,更更簡潔
  28. AsyncStorage 結合 try catch 需要Error handling 錯誤處理理(捕獲錯誤): 錯誤的接收執⾏行行動作—> catch( )

    Await 如果有錯誤發⽣生,只會默默地拋出錯誤 ... 後⾯面的程式碼就無法執⾏行行!
  29. react-redux數據流框架 加入redux 安裝: npm install --save redux npm install --save

    react-redux 新增檔案: 在src內新增資料夾redux, 裡⾯面再新增三個檔案: action.js, reducer.js, store.js
  30. react-redux數據流框架 設置redux action.js export const ADD_TO_COUNTER = 'ADD_TO_COUNTER' export function

    addToCounter(newName) { return { type: ADD_TO_COUNTER, payload: { newName: newName }, } } import { ADD_TO_COUNTER } from './action' const initialState = { newName: 'Avon' } export default function reducer(state = initialState, action) { switch (action.type) { case ADD_TO_COUNTER: return { ...state, newName: action.payload.newName } default: return state } reducer.js 定義action名稱&動作 (action creator) 是否要傳入參參數 action名稱規則:以全⼤大寫表⽰示 監聽action,並依照不同動 作來來更更新狀狀態
 listens to actions and modifies the state 引入action 定義初始狀狀態 傳入初始狀狀態&action 會依據不同的action來來更更新狀狀態 標⽰示動作類型
  31. react-redux數據流框架 設置redux store.js import reducer from './reducer' import { createStore

    } from 'redux' export default function configureStore() { let store = createStore( reducer ) return store } import { Provider } from 'react-redux' import configureStore from './src/redux/store' const store = configureStore() const MyApp = () => ( <Provider store={store}> <App /> </Provider> ) // AppRegistry.registerComponent(appName, () => App); AppRegistry.registerComponent(appName, () => MyApp) index.js 引入reducer 利利⽤用creactStore創建store 引入reducer到store,並讓之後 app進入點可以引入這個store
 connects the reducer to the store 新增兩兩⾏行行 引入store和Provider組件 利利⽤用Provider引入store (Provider把store當成參參數往下傳) 改成下⾯面這⾏行行
  32. react-redux數據流框架 存入&讀取 ProfileScreen.js import { connect } from 'react-redux' import

    { addToCounter } from ‘../redux/action' … <Text>{this.props.newName}</Text> <Button onPress={() => this.props.addToCounter(this.state.name)} title='設定名字2' /> … const mapStateToProps = (state) => { return { newName: state.newName } } const mapDispatchToProps = (dispatch) => { return { // 法⼀一 // addToCounter: (name) => { // dispatch({ type: 'ADD_TO_COUNTER', payload: { newName: name } }) // } // 法⼆二 addToCounter: (name) => dispatch(addToCounter(name)) } } export default connect(mapStateToProps, mapDispatchToProps)(ProfileScreen) 新增兩兩⾏行行 引入connect和動作 讀取:⽤用this.props讀取變數 讀取: 把全域的state “state.newName” 轉變成props, 讓這⾴頁ProfileScreen⽤用this.props.newName來來讀取 觸發動作: 利利⽤用dispatch來來呼叫執⾏行行動作(action),就會觸發 reducer來來返回新的state,connect即會監聽到state 的變化,合併回props action export default 改成此⾏行行 利利⽤用connect來來接收Provider傳入的store, 並把全域參參數state變成props拿來來給此component使⽤用
  33. 備註:⼀一定要放到 componentDidMount const host = 'https://njapp.uitoxbeta.com/app'; const url = `${host}${route}`;

    var params = { "articleId": this._deckSwiper._root.state.selectedItem._id, "userId": this.state.userId, } const options = { method: 'POST', headers: { Accept: 'application/json', authorization: token, 'Content-Type': 'application/json', }, body: (params ? JSON.stringify(params) : null), }; 簡單⽤用法:fetch('Put API Request URL Here', { method: 'get'}) 常⽤用⽅方法:fetch(url, options) Fetch API 常⽤用基本⽅方法
  34. const REQUEST_URL = 'https://www.googleapis.com/books/v1/volumes?q=subject:fiction' fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) =>

    { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.items) }); }) .catch((err) => { console.log('error') }) Fetch API 常⽤用基本⽅方法
  35. state ={ … ds:[ ] } fetch … this.setState({ dataSource:

    this.state.dataSource.cloneWithRows(responseData.items), ds:responseData.items }); … <TouchableHighlight onPress={() => this.pressRow(cases)}> {cases.addToMyLists === true ? <Image style={styles.imageCheck} source={require('../img/square_check.png')} /> : <Image style={styles.imageCheck} source={require('../img/square_non_check.png')} />} </TouchableHighlight> … 讀取data+新增勾選按鈕
  36. pressRow = (cases) => { const newDatas = this.state.ds.map(a =>

    { let copyA = { ...a }; if (copyA.id === cases.id) { copyA.addToMyLists = !copyA.addToMyLists } return copyA; }); this.setState({ ds: newDatas, dataSource: this.state.dataSource.cloneWithRows(newDatas), }); } 複製data, 並依照勾選新增tag
  37. componentDidUpdate() { let chooseCountNum = 0 let getALL = [

    ] this.state.ds.map(a => { if (a.addToMyLists === true) { getALL.push(a) return chooseCountNum += 1 } }) // 儲存&統計所有⾃自選品 this.saveToStorage(getALL) } saveToStorage = async (getMyBooks) => { try { await StorageHelper.setMySetting('code', JSON.stringify(getMyBooks)); } catch (err) { console.error(err); } }; 新增array放入被勾選到的書,並存起來來
  38. state = { myBookCount: 0, myBookListName: [] } loadStorage =

    async () => { this.setState({ myBookListName: [] }) let bookGet = await StorageHelper.getMySetting('code') let a = JSON.parse(bookGet) let newArray = [ ] a.forEach((thing) => { newArray.push(thing.volumeInfo.title) }) this.setState({ myBookCount: a.length, myBookListName: newArray }) }; <Text style={styles.instructions}>我收藏了了{this.state.myBookCount}本書</Text> { this.state.myBookListName.map((bookName, index) => { return (<Text key={index}>書名為:{bookName}</Text>) }) } componentDidMount() { this.props.navigation.addListener('didFocus', () => this.loadStorage()) } 讀取storage內的資料