top of page

RN Navigation series. Part II.React navigation v4



1. Cài thư viện react-navigation

 yarn add react-navigation@ hoặc npm install react-navigation

2. Cài các thư viện bổ trợ cho react-navigation

yarn add react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

Hoặc dùng npm install đều được, các bạn cũng có thể thêm luôn react-navigation vào danh sách cài đặt, ở đây tôi tách ra nhằm nhấn mạnh phần nào chính, phần nào phụ.


Tiếp đến, chúng ta cần link các thư viện này, tuỳ thuộc vào phiên bản react-native mà các bạn sẽ có cách link khác nhau. Lý do cần link và cách link phổ biến, các bạn vui lòng tham khảo “Link native code”. Ở đây tôi tiến hành link các thư viện trên như sau:


React-native 0.60 trở lên

- iOS: sau khi cài đặt xong, các bạn gõ tiếp ở terminal lần lượt:

cd ios
pod install
cd ..

- Android: thêm 2 dòng ở phần dependencies trong file android/app/build.gradle

implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation ‘androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'

React-native 0.59 trở xuống

react-native link react-native-reanimated
react-native link react-native-gesture-handler
react-native link react-native-screens
react-native link react-native-safe-area-context

Đồng thời bạn cũng phải cài jetifier để tương thích với androidX:

yarn add jetifier --dev hoặc npm install jetifier --save-dev
 

Sau đó, thêm đoạn "postinstall": "jetifier -r” vào script trong package.json:

"scripts": {
 "start": "react-native start",
 "test": "jest",
 "postinstall": "jetifier -r"
 }

Chú ý: nhớ bỏ đoạn postinstall đó đi nếu bạn nâng cấp react-native lên phiên bản 0.60 hoặc cao hơn

Tiếp đến, phải cấu hình react-native-gesture-handler cho Android, công đoạn này phải làm thủ công vì Linking và Auto linking không hỗ trợ. Các bạn mở file MainActivity.java lên, chỉnh sửa như sau:


package com.rnforbeginner;
import com.facebook.react.ReactActivity;

// Import 3 đồng chí này
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;


public class MainActivity extends ReactActivity {
 /**
 * Returns the name of the main component registered from JavaScript.
 * This is used to schedule rendering of the component.
 */
 @Override
 protected String getMainComponentName() {
 return "rnforbeginner";
 }

 // Thêm đoạn dưới này
 @Override
 protected ReactActivityDelegate createReactActivityDelegate() {
 return new ReactActivityDelegate(this, getMainComponentName()) {
 @Override
 protected ReactRootView createRootView() {
 return new RNGestureHandlerEnabledRootView(MainActivity.this);
 }
 };
 }
}

Cuối cùng, mở file App.js lên và thêm đoạn import này vào dưới import React from ‘react':

import 'react-native-gesture-handler'


3. Giới thiệu cấu trúc Navigation

Vậy là đã cài đặt xong react-navigation, tiếp đến chúng ta sẽ lần lượt triển khai các kiểu navigation để đạt được kết quả dưới đây:


Ứng dụng tôi tự nghĩ ra gọi là “Photo Sharing Application”, là nơi để mọi người có thể:

- Chia sẻ, lưu trữ ảnh yêu thích

- Mua ảnh của người khác

- Xếp hạng ảnh được yêu thích, ảnh được mua nhiều nhất

Với mục đích demo navigation, ngoài tab Home, các màn khác sẽ chỉ là các màn trắng với Header và Title.


Trước khi làm bất cứ một ứng dụng, các bạn nên hình dung trong đầu cấu trúc navigation của ứng dụng đó sẽ như thế nào, vẽ ra được diagram là tốt nhất, để có thể dễ dàng maintain về sau, đặc biệt khi có người khác tiếp quản app. Ứng dụng càng phức tạp, công đoạn chuẩn bị càng quan trọng.


Có rất nhiều cách tổ chức cấu trúc navigation, càng làm nhiều, các bạn sẽ càng đúc rút ra được cho mình kinh nghiệm và kỹ năng làm navigation structure, nên đừng quá lo lắng nhé.

Ứng dụng nho nhỏ này sẽ có cấu trúc navigation như sau:


Photo Sharing Application Navigation Structure
Photo Sharing Application Navigation Structure
  • App của chúng ta được bao gọn trong một StackNavigation, gọi là App Stack.

  • Ngay khi vào App Stack, Drawer Navigation (Side Navigation) xuất hiện. Drawer này cần một Screen khởi điểm để hiển thị, vì vào app mà Drawer hiện ra ngay thì không hợp lý chút nào. Screen khởi điểm đó chính là Home Stack, được nằm trong Bottom Tab Navigation.

  • Bottom Tab Navigation này chứa 4 Tabs chính, mỗi tab là một Stack Navigation: Home, Shopping, Ranking và Profile.

  • Từ màn Home, ấn vào từng thẻ ảnh của album để vào xem chi tiết thẻ ảnh.

4. Cấu trúc thư mục

Như đã nói, với mục đích demo nhanh cách hoạt động của navigation, với cấu trúc thư mục , tôi cũng sẽ cấu trúc khá đơn giản:


Ở đây chúng ta quan tâm tới các phần chính:

- File App.js

- Folder src là toàn bộ source code chính, hiện tại chứa folder Components và folder app. Trong ứng dụng thực tế, folder src sẽ có thêm nhiều folder khác (vd như thư mục module hoá, tài nguyên, ….)

- Folder app chứa folder navigator mà chúng ta sẽ cấu trúc navigation ở đây.

- Folder Component chứa các React-native Components mà cụ thể là các màn hình sẽ được hiển thị lên app, cùng với một vài màn dùng chung. Dĩ nhiên ở app thực tế, chúng ta sẽ tách các màn dùng chung ra.


5. Triển khai

5.1. Tạo màn hình


Trong folder navigators, tạo thư mục và file:

- Drawer/index.js

- index.js


Trong folder Components, tạo các thư mục và các file:

- Home/index.js

- Home/Detail/index.js (tạo một thư mục Detail trong Home)

- Shopping/index.js

- Ranking/index.js

- Profile/index.js

- Header/index.js

- index.js


File index.js trong folder Home chính là file FlatList tôi lấy từ chương FlatList. Các màn khác ngoài màn Header và Drawer, chúng ta sẽ để đơn giản nhất có thể, cụ thể với màn Shopping:


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

const Shopping = () => {
 return (
 <View>
 <Text>Shopping Screen</Text>
 </View>
 )
}
export default Shopping

Các màn khác làm tương tự. Riêng với màn Header, tôi sẽ giải thích khi làm navigation structure. Tiếp đến, vào file index.js, gom các màn lại và export một lượt:

export { default as Home } from './Home'
export { default as HomeDetail } from './Home/Detail'
export { default as Shopping } from './Shopping'
export { default as Ranking } from './Ranking'
export { default as Profile } from './Profile'
export { default as Header } from './Header'

Mục đích của việc này để khi ở các màn khác, các bạn có thể import các màn trên thuận tiện hơn, dễ quản lý hơn. Để tôi minh chứng trong phần cấu trúc navigation.

5.2. Cấu trúc Navigation

Import thư viện và các màn:

import React from 'react'
import { Dimensions } from 'react-native'

import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import { createBottomTabNavigator } from 'react-navigation-tabs'
import { createDrawerNavigator } from 'react-navigation-drawer'

// Phục vụ cho icon ở Bottom tab navigation
import IonIcon from 'react-native-vector-icons/Ionicons'

// Thay vì mất 5 dòng code import từng màn, ở đây chỉ cần 1 dòng
import { Home, Shopping, Ranking, Profile, HomeDetail } from '../src/Components'

import Drawer from './Drawer'

Tạo HomeStack, ShoppingStack, RankingStack, ProfileStack, BottomNav. DrawerNav, AppStack

Theo đúng diagram trên, tôi sẽ lần lượt tạo các Stack và Bottom, Drawer Nav, AppStack kèm theo giải thích ở từng phần:

  • initialRoutName: thuộc tính này sẽ thông báo cho các Navigation biết rằng khi bắt đầu vào app thì màn nào sẽ vào đầu tiên, điều này phụ thuộc vào business của bạn.

  • headerMode: react-navigation sẽ cung cấp một header mặc định cho các màn khi Navigation khai báo. Nhưng tôi muốn dùng Header của riêng tôi để có thể style theo ý thích của mình, tôi sẽ để headerMode = ‘none’, có 2 thuộc tính khác là ‘float’ và ‘screen’ đem lại mỗi kiểu animation khác nhau khi chuyển màn, bạn có thể tự trải nghiệm từng cái để xem cách hoạt động.

const HomeStack = createStackNavigator(
 {
 Home,
 HomeDetail
 }, {
 initialRouteName: 'Home',
 headerMode: 'none'
 }
 )
 
 const RankingStack = createStackNavigator(
 {
 Ranking
 }, {
 initialRouteName: 'Ranking',
 headerMode: 'none'
 }
 )
 
 const ProfileStack = createStackNavigator(
 {
 Profile
 }, {
 initialRouteName: 'Profile',
 headerMode: 'none'
 }
 )
 // Hàm này dùng để render icon theo tên màn
 const renderIconName = routeName => {
 switch (routeName) {
 case 'Home':
 return 'ios-home'
 case 'Shopping':
 return 'md-basket'
 case 'Ranking':
 return 'ios-flame'
 case 'Profile':
 return 'ios-glasses'
 default:
 break
 }
 }
  • defaultNavigationOptions: cấu hình mặc định cho Navigation, tất cả các kiểu Navigation đều có thuộc tính này. Tuỳ vào design, business logic mà sử dụng cho hợp lý. Trong trường hợp này, tôi muốn render icon cho từng tab, vì thế tôi sẽ dùng tabBarIcon trong đây.

  • tabBarIcon chứa nhiều thuộc tính, ở đây tôi chọn tintColor, để xác đổi màu màu khi tab active hoặc tab inactive, màu này được quyết định ở tabBarOptions

  • tabBarOptions cũng có nhiều thuộc tính, và tôi chọn activeTintColor (màu khi tab active), inactiveTintColor (màu khi tab inactive)

Tham khảo đầy đủ các tuỳ chọn cho tabBarOptions ở tài liệu của react-navigation: https://reactnavigation.org/docs/en/bottom-tab-navigator.html#tabbaroptions


const BottomTabNav = createBottomTabNavigator(
 {
 Home: HomeStack,
 Shopping: ShoppingStack,
 Ranking: RankingStack,
 Profile: ProfileStack
 },
 {
 initialRouteName: 'Home',
 defaultNavigationOptions: ({ navigation }) => ({
 tabBarIcon: ({ tintColor }) => {
 const { routeName } = navigation.state
 let iconName = renderIconName(routeName)
 return <IonIcon name={iconName} size={22} color={tintColor} />
 }
 }),
 tabBarOptions: {
 activeTintColor: '#EB5757',
 inactiveTintColor: '#888'
 }
 }
 )
  • Trong hầu hết các trường hợp, việc sử dụng một Drawer tự tạo là việc cần thiết để có thể chúng ta có thể kiểm soát hoàn toàn UI lẫn logic của Drawer, do vậy tôi dùng đến contentComponent và trỏ tới Drawer đã tạo của mình.

  • drawerWidth: xác định độ rộng của Drawer, ở đây tôi cho nó rộng bằng 1.5 màn hình

Tham khảo đầy đủ các tuỳ chọn cho Drawer ở tài liệu của react-navigation: https://reactnavigation.org/docs/en/4.x/drawer-navigator.html


const DrawerNav = createDrawerNavigator(
 {
 Tab: BottomTabNav
 }, {
 contentComponent: Drawer,
 drawerWidth: Dimensions.get('window').width / 1.5
 }
)
const AppStack = createStackNavigator(
 {
 Drawer: DrawerNav
 }, {
 initialRouteName: 'Drawer',
 headerMode: 'none'
 }
)
// Cuối cùng, gói App Stack trong High Order Component createAppContainer
// Và export để sử dụng trong App.js
export default createAppContainer(AppStack)

Nhưng trước khi import Navivation structure vào App.js, tôi sẽ làm Header và Drawer trước. Đây là Header.js:


import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'

export default class Header extends Component {

 render() {
 const { navigation, title, back } = this.props
 return (
 <View style={styles.wrapper}>
 <Icon
 name={back? 'ios-arrow-back' : 'ios-menu'}
 color={'gray'}
 size={30}
 // Nếu có props back truyền vào, chúng ta sẽ gọi hàm goBack()
 // để quay lại màn trước đó, nếu không thì gọi hàm toggleDrawer
 // để đóng mở Drawer
 onPress={() => back ? navigation.goBack() : navigation.toggleDrawer()}
 />
 <Text style={styles.headerTitle}>{title}</Text>
 <View />
 </View>
 )
 }
}
const styles = StyleSheet.create({
 wrapper: {
 height: 60,
 width: '100%',
 flexDirection: 'row',
 justifyContent: 'space-between',
 alignItems: 'center',
 backgroundColor: 'white',
 paddingTop: 15,
 paddingHorizontal: 15,
 borderStyle: 'solid',
 borderBottomWidth: 0.5,
 borderBottomColor: '#ddd'
 },
 headerTitle: {
 textTransform: 'uppercase',
 textAlign: 'center',
 fontWeight: '600',
 fontSize: 18
 }
})

Sau đó, import component Header này vào các màn, tôi làm với màn Home và màn Detail, các màn còn lại các bạn tự thêm nhé, và ở đây tôi chỉ viết phần trong render:

  • Home:

render() {
 return (
 <View style={styles.container}>
 <Header
 navigation={this.props.navigation}
 title={'Home'}
 />
 <Text style={styles.title}>My Photo Album</Text>
 {this.renderFlatList()}
 </View>
 )
}
  • Detail:

render() {
 return (
 <View>
 <Header
 navigation={this.props.navigation}
 title={'Detail'}
 back
 />
 <Text>Detail</Text>
 </View>
 )
}

Còn đây là Drawer.js:



import React from 'react'
import { View, Text, StyleSheet } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'

const Drawer = () => {
 return (
 <View style={styles.wrapper}>
 <View style={styles.itemWrapper}>
 <Icon
 name='md-settings'
 color={'#EB5757'}
 size={30}
 />
 <Text style={styles.itemText}>Setting</Text>
 </View>
 <View style={styles.itemWrapper}>
 <Icon
 name='ios-exit'
 color={'#EB5757'}
 size={30}
 />
 <Text style={styles.itemText}>Logout</Text>
 </View>
 </View>
 )
}
export default Drawer


const styles = StyleSheet.create({
 wrapper: {
 flex: 1,
 justifyContent: 'center',
 alignItems: 'center'
 },
 itemWrapper: {
 flexDirection: 'row',
 alignItems: 'center',
 marginBottom: 20
 },
 itemText: {
 marginLeft: 10,
 fontSize: 15,
 fontWeight: '600',
 textTransform: 'uppercase'
 }
})

5.3. Import vào App.js

Các bạn mở App.js lên và import Navigation vào:

import Navigator from './src/app/navigators'
const App = () => {
 return (
 <View style={styles.container}>
 <Navigator />
 </View>
 )
}

Khi vào màn Detail, Header sẽ trở thành như này:



Nhấn vào Icon mũi tên back, bạn sẽ được quay về màn Home. Có một chi tiết nhỏ, khi vào Detail, bạn có thể thấy BottomTab vẫn hiển thị. Trong một số trường hợp, chúng ta sẽ muốn ẩn phần này đi. Các bạn hãy coi như đây là một bài tập nho nhỏ nhé.








Comments


Screen Shot 2020-10-23 at 13.32.24.png

Hi, thanks for stopping by!

Thank you for visiting my blog. Here I'd love to share some thoughts about my professional, my start-up journey, or just a cute picture of my son.

Let the posts
come to you.

Thanks for submitting!

  • Facebook
  • Instagram
  • Twitter
bottom of page