# react-native-app
**Repository Path**: lukangfeng/react-native-app
## Basic Information
- **Project Name**: react-native-app
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2019-07-30
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# React-Native踩坑
[TOC]
## 配置
1. 配置依赖
2. 配置路径别名
3. 配置路由router
4. 配置仓库store
5. 配置~~Sass~~,采用css,scss文件自动转换成react native直接引用的样式文件
### 配置依赖
> [插件总结](https://www.jianshu.com/p/d6f216e7b358)
1. 安装UI框架 react-base
```
yarn add native-base
```
2. 安装路由 react-native-router-flux【自带顶部栏与底部Tab选项栏】
```
yarn add react-native-router-flux
```
3. 安装图标 react-native-vector-icons【底部选项的图标也可以使用】
```
yarn add react-native-vector-icons
```
4. 时间格式 moment
```
yarn add moment
```
5. 数据请求 axios
```
yarn add axios
```
6. 图片处理(ps:自适应等)react-native-fit-image :对于不同的移动尺寸,以响应式风格制作图像
```
yarn add react-native-fit-image
```
7. 时间处理 react-native-timeago 此包有助于将时间戳转换为时间前的文本
```
yarn add react-native-timeago
```
8. 第三方轮播 react-native-swiper
```
yarn add react-native-swiper
```
9. redux的中间件redux-saga
```
yarn add redux-saga
```
10. redux中间件 redux-saga / redux-logger
```
yarn add redux-saga redux-logger
```
### 配置别名
配置tsconfig.json
> "@/*": ["*"] ,单独src的别名-----》package.json>>>{"name": "@"}
```
"baseUrl": "src",
"paths": {
"@components/*": ["components/*"],
"@views/*": ["views/*"],
"@/*": ["*"]
}
```
最后在views目录下新建package.json
```
{
"name": "@views"
}
```
### 配置路由
router/index.ts
```
import Home from "@views/home";
import Product from "@views/product";
import Sort from "@views/sort";
import Cart from "@views/cart";
import User from "@views/user";
export default {
Home,
Product,
Sort,
Cart,
User
};
```
### 配置仓库
#### api配置
采用axios配置api,(一种配法)**因业务/项目来变更配置**,大致的不会变
```
import axios from "axios";
let defaultConfig = {
timeout: 3000,
baseURL: "https://climber-zkc.xyz/api/react-mobile-mall"
};
//axios,初始化
let instance: any = axios;
//定义一个类
class Axios {
//构造函数,创建一个类
constructor(props) {
//props是undefind或者不是object就使用默认配置
if (props && typeof props == "object") {
instance = axios.create(props);
} else {
instance = axios.create(defaultConfig);
}
//请求拦截器
instance.interceptors.request.use(
config => {
console.log(config);
//直接发送请求,不做任何处理
return config;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
//响应拦截器
instance.interceptors.response.use(
response => {
//直接返回请求结果,不做任何处理
return response.data;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
}
//send方法
send(params: any) {
console.log(params);
if (!params || typeof params != "object") {
throw new Error("params is undefined or not an object");
}
if (params.method == "get") {
return get(params.url);
} else if (params.method == "post") {
return post(params.url);
} else if (params.method == "put") {
return put(params.url);
} else if (params.method == "patch") {
return patch(params.url);
} else if (params.method == "delete") {
return deletes(params.url);
}
}
}
//异步get请求类型的方法
async function get(url: string) {
try {
let response = await instance.get(url);
return response;
} catch (e) {
console.log(e);
}
}
//异步post请求类型的方法
async function post(url: string) {
try {
let response = await instance.post(url);
//return callback(response)
return response;
} catch (e) {
console.log(e);
}
}
//异步delete请求类型的方法
async function deletes(url: string) {
try {
let response = await instance.delete(url);
//return callback(response)
return response;
} catch (e) {
console.log(e);
}
}
//异步put请求类型的方法
async function put(url: string) {
try {
let response = await instance.put(url);
//return callback(response)
return response;
} catch (e) {
console.log(e);
}
}
//异步patch请求类型的方法
async function patch(url: string) {
try {
let response = await instance.patch(url);
//return callback(response)
return response;
} catch (e) {
console.log(e);
}
}
let Instance = new Axios({
timeout: 8000,
baseURL: "https://climber-zkc.xyz/api/react-mobile-mall"
});
//暴露实例化这个Axios类里面的方法
export default Instance;
```
#### 请求配置
```
import HttpUtils from "./https";
class Https {
newsLists = (parmas: any) => {
return HttpUtils.send({
url: `/news?page=${parmas}&pagesize=8`,
method: "get"
});
};
}
export default new Https();
```
#### 仓库配置
```
//利用createStore把reducer数据中心的属性或者方法映射到store,之后通过Provider传递给组件
//applyMiddleware,添加中间件
//applyMiddleware 函数的作用就是对 store.dispatch 方法进行增强和改造,使得在发出 Action 和执行 Reducer 之间添加其他功能。
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import mySaga from "../sagas";
import reducer from "../reducers";
//实例化redux-saga的createSagaMiddleware创建一个saga方法中间件
const sagamiddleware = createSagaMiddleware();
//配置仓库
export default () => {
//sagamiddleware中间件加入到middlewaress数组
const middlewares = [sagamiddleware];
//React Native中有一个全局变量__DEV__用于指示当前运行环境是否是开发环境
if (__DEV__) {
//开发环境引入logger
middlewares.push(logger);
}
//引用中间件创建仓库
const createStoreMiddleware = applyMiddleware(...middlewares)(createStore);
//创建数据中心连接仓库
// const store = createStore(reducer, applyMiddleware(...middlewares));
const store = createStoreMiddleware(reducer);
// console.log(store);
//运行saga,或者这样写sagaMiddleware.run(mySaga),mySaga是引入的saga文件
sagamiddleware.run(mySaga);
return store;
};
```
#### 简单述说一下仓库流程(react-redux可以深入---》有个按需引用)
##### 1. 建立行为【Action】
```
const News = {
GET_MANY_NEWS: "GET_MANY_NEWS"
};
//action这边只定义(规范)所有的行为类型type
export function fetchNewData(parmas) {
// console.log('parmas')
return {
type: News.GET_MANY_NEWS,
parmas
};
}
```
##### 2. 因为运用了redux-saga--》(ps采用了ES6的Generator/yield* 表达式)中间件(解决异步的),建立监控行为(简单应用)----》这里可以深入
1. takeEvery
用来监听action,每个action都触发一次,如果其对应是异步操作的话,每次都发起异步请求,而不论上次的请求是否返回。
2. takeLatest
作用同takeEvery一样,唯一的区别是它只关注最后,也就是最近一次发起的异步请求,如果上次请求还未返回,则会被取消。
3. call
call用来调用异步函数,将异步函数和函数参数作为call函数的参数传入,返回一个js对象。saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。
4. put
put是saga对Redux中dispatch方法的一个封装,调用put方法后,saga内部会分发action通知Store更新state。这个借口主要也是为了方便我们写单元测试提供的。
5. all
all提供了一种并行执行异步请求的方式。之前介绍过执行异步请求的api中,大都是阻塞执行,只有当一个call操作放回后,才能执行下一个call操作, call提供了一种类似Promise中的all操作,可以将多个异步操作作为参数参入all函数中,如果有一个call操作失败或者所有call操作都成功返回,则本次all操作执行完毕
---参考[Redux-saga--API](https://redux-saga.js.org/docs/api/)
```
import https from "@/api";
import { all, call, put, takeLatest } from "redux-saga/effects";
export function* newsList(actions: any) {
try {
const data = yield call(https.newsLists, actions.parmas);
yield put({
type: "GETSUCCESS",
data
});
} catch (err) {
yield put({
type: "GETFAIL"
});
}
}
//默认暴露先执行
export default function* root() {
// console.log("object");
//takeLatest解决并发操作
yield all([takeLatest("GET_MANY_NEWS", newsList)]);
}
```
##### 3. Reducer数据处理中心处理返回回来的行为type做出数据处理
```
const initState = {
newsList: []
};
//数据处理中心(通过方法/请求数据等都需要经过reducer)
//基本从actions来
const Newslist = (state = initState, actions) => {
//接收到Succsee,就把接收到的数据存放到newsList
// console.log(actions.data);
switch (actions.type) {
case "GETSUCCESS":
//合并数据到data里
return { ...state, newsList: actions.data };
case "GETFAIL":
//合并数据到data里
return state;
default:
return state;
}
};
export default Newslist;
```
##### 4. 组件的处理就是触发事件(触发行为)
```
//引入action的规范
import * as homeAction from "@/actions/home";
//bindActionCreators不需要子组件知道有redux
import { bindActionCreators, Dispatch } from "redux";
//连接state和props映射
import { connect } from "react-redux";
--------------------------------------------------------------------------
//获取数据(新闻)
getList = async () => {
//这边只发起请求
const {
actions: { fetchNewData }
} = this.props;
await fetchNewData(1);
};
componentDidMount() {
this.getList();
}
---------------------------------------------------------
//State属性映射
const mapStateToProps = (state: any, props: any) => ({
Data: state.home.newsList //传递数据到Data,子组件接收到data
});
//映射异步请求方法(发送homeAction行为)
const mapDispatchToProps = (dispatch: Dispatch) => ({
actions: bindActionCreators(homeAction, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home);
```
#### 配置~~Sass~~,css/sass文件自动转换成react-native样式文件。
[介绍出处地址](http://bbs.reactnative.cn/topic/6604/web前端开发rn的福音-css-scss文件自动转换成react-native直接引用的样式文件)
在react-native用sass预处理css是个很糟糕的注意,比如react-native没有像素单位,sass必须带单位
## 存在的共有组件部分
1. 轮播
2. 下拉刷新/上拉加载
3. 九宫格
4. 。。。。。
## 架构App.tsx主模块
安装路由模块
```
yarn add react-native-router-flux
```
配置底部tab选项卡
ps:**react-native-router-flux**自带tab选项卡与顶部栏
解读:
1. Stack是为堆栈,先进后出【Tab选项卡】,(基于堆栈实现的导航:场景组合在一起的组件)
1. 需要有唯一的key,title属性是属于Tab选项卡的title
2. Scene是为场景,多个场景可以被Stack包裹,公用顶部/底部等,**必须要有一个唯一的 key**
1. title为顶部栏的名字
2. initial是设置默认页
3. navBar设置自定义顶部栏
3. Stack设置图标
1. icon属性引入组件
2. iconName在Stack不存在该属性,需要改源代码iconName?:string,iconName是传递给TabIcon的图标名
3. TabIcon组件源代码
4. Tab属性是配置底部Tab选项卡

### 配置底部图标组件
**import Icon from "react-native-vector-icons/SimpleLineIcons**,很重要,没有大括号
```
import React from "react";
// 引入SimpleLineIcons图标库
import Icon from "react-native-vector-icons/SimpleLineIcons";
const TabIcon = props => {
return (
);
};
export default TabIcon;
```
### 主组件路由基础源代码
#### 路由
```
。。。。。
```
#### 自定义底部导航栏
**使用StatusBar,可以使得状态栏消失等操作**
```
```
**获取状态栏高度的方法**
[底部状态栏的解析](https://juejin.im/post/5c4949bc6fb9a049bd42a6eb)
//调用方法
```
const STATUS_BAR_HEIGHT = isiOS()
? isiPhoneX()
? 34
: 20
: StatusBar.currentHeight;
```
//获取方法
```
import { Dimensions, Platform } from "react-native";
// iPhone X、iPhone XS
const X_WIDTH = 375;
const X_HEIGHT = 812;
// iPhone XR、iPhone XS Max
const XSMAX_WIDTH = 414;
const XSMAX_HEIGHT = 896;
const DEVICE_SIZE = Dimensions.get("window");
const { height: D_HEIGHT, width: D_WIDTH } = DEVICE_SIZE;
export const isiOS = () => Platform.OS === "ios";
export const isiPhoneX = () => {
return (
(isiOS() &&
((D_HEIGHT === X_HEIGHT && D_WIDTH === X_WIDTH) ||
(D_HEIGHT === X_WIDTH && D_WIDTH === X_HEIGHT))) ||
((D_HEIGHT === XSMAX_HEIGHT && D_WIDTH === XSMAX_WIDTH) ||
(D_HEIGHT === XSMAX_WIDTH && D_WIDTH === XSMAX_HEIGHT))
);
};
```
## Home主页
### 轮播
采用第三方库react-native-swiper---写成组件
完整代码见components/Banner/index.tsx
唯一需要注意的是(本地图片文件可以这么暴露)
```
export default {
banner1: require("@/assets/banner/banner1.jpg"),
banner2: require("@/assets/banner/banner2.jpg"),
banner3: require("@/assets/banner/banner3.jpg"),
banner4: require("@/assets/banner/banner4.jpg"),
banner5: require("@/assets/banner/banner5.jpg")
};
```
### 九宫格
完整代码见components/Grid/index.tsx
```
注意事项:
1. 父组件需要传递的数据
2. 传递怎样的数据九宫格才是可行的
3. 样式Bug的问题
```
### 再次封装FlatList
[React--typeScript--hook推荐学习](https://github.com/rrd-fe/blog/blob/master/react/typescript-hooks.md)
重构整个FlatList组件(增加复用性,使用Hook)
1. 定义父组件可以传递的数据有什么(用户可以自定义一些什么)
#### 父组件可以传递什么
**通用性props属性**
1. 毋庸置疑**数据**是最关键的*ListData*
------------------------------------------------------------这俩个是最基础的属性(形成了一个基础列表list)
2. 毋庸置疑的是用户自定义行显示渲染的格式*renderContent*
3. 写到后面,发现还需要发起请求,ListData只是一个管道而已,需要requestApi
-----》props拥有listData,为了性能优化---》state拥有listData(hook使用useState)
先显示好吧,数据内容一点点增加集成
1. 集成下拉刷新功能
##### 完成list表单显示
----->
```
//没啥可说的,先引入React
import React, { useState, useEffect, useLayoutEffect } from "react";
//引入基础组件FlatList
import { FlatList } from "react-native";
interface IsProps {
ListData: any; //父组件传回的数组数据
renderContent: any; //父组件传回的一行的渲染内容
requestApi: any; //向父组件发起请求的数据的接口(方法),为了展示的性能考虑,(页面于限制条数暂时固定死,以后再次考虑是否有必要)
}
//Hook首先只有props
const ListFresh = (props: IsProps) => {
const [listData, setListData] = useState([]); //定义空数组(所有数据存放的数组,里面的结构随着接口而变的,因此就不规定长啥样子了)
const [page, setPage] = useState(1); //初始化定义页码为1
//数据最开始利用userEffect发起请求(同步发起请求)
useLayoutEffect(() => {
// console.log(props.ListData);
//数据挂载的时候发起请求,默认请求第一条数据
requestData(page);
}, [page]); //依赖返回page数据驱动useEffect
useEffect(() => {
//异步渲染数据
setListData(props.ListData);
}, [props.ListData]);
//这边需要拥有一个专门向父组件发起请求的函数,有必要每次请求注明请求那一页数据
const requestData = async (page: number) => {
await props.requestApi(page);
};
//每一行渲染的的循环,通过父组件传入数据渲染(data是渲染数据)
const renderMain = (data: any) => {
return {props.renderContent(data)};
};
//--》data/renderItem是FlatList最最最基本的属性,缺一不可,形成列表一个都不能少
return renderMain(data)} />;
};
//帮助我们控制合适渲染组件
//检查接下来的渲染props于前一次是否相同,一样则会保留上一次(性能优化)
export default React.memo(ListFresh, (prevProps, nextProps) => {
return nextProps.ListData === prevProps.ListData;
});
```
###### 产生一个问题(初始状态请求渲染慢导致的白屏)
---------------------------------------------------------------------------
官方文档--》先对FlatList性能进行优化
1. keyExtractor属性指定使用 id 作为列表每一项的 key,提高性能
```
keyExtractor={data => {
return data._id.$oid;
}}
```
2.getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout用起来就既高效又简单
3.initialNumToRender,必选,用于指定一开始显示的条数,用最短的时间给用户呈现可见的内容
###### 基础样式优化
1. 增加行与行之间的分割线ItemSeparatorComponent
===自定义分割线之后思考到可能不需要分割线的时候,给出属性来,父组件来判断
2. 增加空数据时候的UI设计(暂时解决初始白屏)
3. ListEmptyComponent初始请求判断数据长度,显示占位与请求指示器
--------------------------------------------------------------------------------------
属性 | 解释 | 类型
|----------------------------|------------------------------|-------------------------|
ListData 必要 | 父组件传递过来的数据 |any(数据结构不一定,因此不设成数组[])|
renderContent 必要 |父组件需要传递每一行渲染结构| any|
requestApi 必要 |父组件发起数据请求的 | any|
itemSpearator 可选 | true或者不填,默认需要有分割线| boolean|
createSpearator 可选 |自定义分割线 | any|
itemHeight 必选 | 父组件传回每行的高度,增加性能|number|
initNumber 必选 |指定一开始渲染的元素数量|number|
### --force-with-lease(git)(特殊情况,善用)
当本地添加远程仓库,历史不一致可以使用(上传到远程,使得远程与本地仓库一致)
> without specifying the expected value, will protect the named ref (alone), if it is going to be updated, by requiring its current value to be the same as the remote-tracking branch we have for it.