React Native project initial (更新:2019.03.13) === ## 摘要 >這份文件蒐集 react native 專案 initial 前置步驟。follow 一樣的步驟、和其他前輩建立同樣的環境,能加速日後(頻繁的)debug。 ## 目錄 [TOC] --- 一、環境: --- ### 1. iterm2 + oh-my-zsh https://pjchender.blogspot.tw/2017/02/mac-terminal-iterm-2-oh-my-zsh.html ### 2. nvm install (更新:2019.03.13) 照 nvm 官網 注意:要先安裝 zsh 再安裝 nvm, npm,否則 $PATH 仍會指向 bash,還要去調環境變數才抓得到 npm (更新:2019.03.13) ### 3. 安裝模擬器 照 react-native 官網 ### 4. 安裝 react-native-debugger https://github.com/jhen0409/react-native-debugger simulator 要 command + D open "Remote JS debugging" --- 二、Initial --- 相關指令可參考 package.json 或輸入 yarn run 查看全部 ### 1. 安裝node module ``` $ yarn install ``` ### 2. initial 模擬器 **iOS環境:** ``` $ yarn start // 開啟另一個terminal,並前往當前project路徑下執行此命令。 $ yarn install:ios --simulator="iPhone 7s" ``` **Android 環境:** ``` $ yarn start // 開啟另一個terminal,並前往當前project路徑下執行此命令。 $ yarn install:android ``` **yarn install:android 前須注意:** 1. 模擬機需開啟 - api 選 28+ (更新:2018.11.27) 2. google service 需安裝 - Android Studio preference/appearance & behavior/system settings/android sdk/Google play service version 49 (更新:2018.11.27) 因firebase 所需 3. Java 版本 - [8 (更新:2018.11.27)](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html "jdk8") 4. sdk、ndk 照 [官網教學 0.55 (更新:2018.11.27)](https://facebook.github.io/react-native/docs/0.55/building-from-source) 安裝,ndk 安裝 [r10b 版本 (更新:2019.03.13)](https://dl.google.com/android/repository/android-ndk-r10b-darwin-x86_64.zip) **萬一需重新安裝 (例如:android 有相關安裝錯誤),follow 以下指令:** 先中斷 server 後, ``` $ cd android (現在不用了) $ ./gradlew clean // for clean android related setting (現在不用了) $ cd .. (現在不用了) $ yarn clean // clean nodemodule $ yarn // install nodemodule $ yarn install:android // 一樣在下指令前,須檢查注意事項 ``` **每次啓 android 模擬器後,需輸入以下設定,才能登入:** ``` $ adb reverse tcp:9999 tcp:9999 $ adb reverse tcp:9998 tcp:9998 ``` ### 3. yarn generator 新增檔案 ![](https://i.imgur.com/AmkXP9J.png) ### 4. yarn test api ![](https://i.imgur.com/Fj4J8VE.png) ### 5. 專案會自動更新 node version 每次進入目錄後便會自動安裝,當升級 node 版本時,需移除一檔案: ``` $ rm -rf .prerequisite ``` 選 default node version 時,需下指令: ``` $ node list ``` 三、認識套件 --- 這個專案使用一些外部套件,陳列如下: ### react-native-google-analytics-bridge GA related. * official website: https://github.com/idehub/react-native-google-analytics-bridge ### React Navigation 其中的切換 tab 功能 tabBarOnPress * official website(v1): https://v1.reactnavigation.org/docs/tab-navigator.html * demo - tabBarOnPress: ``` const MyTabNavigator = TabNavigator({ Track: { screen: MyStackNavigator, }, Details: { screen: FundDetailsScreen, } }, { tabBarPosition: 'bottom', tabBarOptions: { upperCaseLabel: false, activeTintColor: '#7562DB', inactiveTintColor: '#7562DB', pressColor: '#7562DB', indicatorStyle: { backgroundColor: '#6458A8' }, style: { backgroundColor: '#fff' } }, navigationOptions: ({ navigation }) => ({ tabBarOnPress: (scene, jumpToIndex) => { console.log('onPress:', scene.route); jumpToIndex(scene.index); }, }), }); ``` ### DvaJS * official website: https://dvajs.com * demo - model: ``` app.model({ // namespace: model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间。 namespace: 'todo', // state: 初始值,优先级低于传给 dva() 的 opts.initialState。 state: [], // reducers: 以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state 的地方。由 action 触发。 reducers: { add(state, { payload: todo }) { // 保存数据到 state return [...state, todo]; }, }, // effects: 以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由 action 触发,可以触发 action,可以和服务器交互,可以获取全局 state 的数据等等。 effects: { *save({ payload: todo }, { put, call }) { // 调用 saveTodoToServer,成功后触发 `add` action 保存到 state yield call(saveTodoToServer, todo); yield put({ type: 'add', payload: todo }); }, }, // subscriptions: 以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。格式为 ({ dispatch, history }, done) => unlistenFunction。注意:如果要使用 app.unmodel(),subscription 必须返回 unlisten 方法,用于取消数据订阅。 subscriptions: { setup({ history, dispatch }) { // 监听 history 变化,当进入 `/` 时触发 `load` action return history.listen(({ pathname }) => { if (pathname === '/') { dispatch({ type: 'load' }); } }); }, }, }); // reference official website: https://dvajs.com/api/#model ``` ### withFormik HoC * brief intro: Wrap our **form** with the using withFormik HoC * official website: https://github.com/jaredpalmer/formik#the-gist * demo ``` import { withFormik } from 'formik'; const form = withFormik({ // Transform outer props into form values mapPropsToValues: props => ({ email: props.email, }), validationSchema: props => { Keyboard.dismiss(); if (props.showCaptcha) { return object().shape({ email: string() .email('Email 格式怪怪的? 請確認一下 : )') .required('請填入註冊時驗證的 Email : )'), password: string().required('請填入密碼'), captcha: string().required('請填入驗證碼'), }); } else { return object().shape({ email: string() .email('Email 格式怪怪的? 請確認一下 : )') .required('請填入註冊時驗證的 Email : )'), password: string().required('請填入密碼'), }); } }, // Submission handler handleSubmit: ( values, { props, setSubmitting } ) => { // setSubmitting(false); Keyboard.dismiss(); props.doSignIn(values); }, validateOnChange: false, displayName: 'form(Login)', }); // !!! Wrap our form with the using withFormik HoC export default form(Component); //reference component: src/landing/components/Login/form.js ``` ### Immer * official website: https://www.npmjs.com/package/immer * brief intro: you will apply all your changes to a temporarily draftState, which is a proxy of the currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state. ![](https://i.imgur.com/bpVXEx3.png) * basic concept `produce(currentState, producer: (draftState) => void): nextState` * demo ``` import produce from "immer" const baseState = [ { todo: "Learn typescript", done: true }, { todo: "Try immer", done: false } ] const nextState = produce(baseState, draftState => { draftState.push({todo: "Tweet about it"}) draftState[1].done = true }) // reference: official website ``` ``` import produce from 'immer'; export default (state, { payload }) => produce(state, draftState => { draftState.activate = true; draftState.close = false; }); // reference component: src/landing/states/cac/reducers/internals/activate.js ``` ### react-native-mimetype > https://www.npmjs.com/package/react-native-mimetype` ``` import mime from 'react-native-mimetype'; const UploadFile = async ({ onUploadFile, response, setFieldValue, downloads }) => { const uri = new URI(response.uri); const filename = response.fileName || uri.filename(); const mimeType = mime.lookup(filename); const data = await onUploadFile({ filename, contentType: mimeType, fileUri: response.uri }); if (data.code === 200) { const file = data.data; const targetDownloads = assign({}, downloads, file); console.log(targetDownloads); setFieldValue('downloads', file); tracker.trackEvent('Activity', 'ActFileSuccess', { label: mimeType }); } }; ``` * MIME type: > https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types > 四、其他操作 --- ### 更換字體方式 > https://medium.com/@kswanie21/custom-fonts-in-react-native-tutorial-for-ios-android-76ceeaa0eb78