# React08
# RN 学习
# 多列布局
// rnc react native component
import React, { Component } from "react";
// 组件或者API使用时,需要提前导入
import { Text, View, FlatList, Dimensions, Image } from "react-native";
// 获取屏幕的宽高
const { height, width } = Dimensions.get("window");
// rpx换算为物理像素 使用方法 例如:30rpx rpx(30)
const rpx = (x) => (width / 750) * x;
export default class App extends Component {
// 实现一个加载网络请求获取数据,并显示到页面 需要经过以下步骤:
// 发送请求 接收数据 存本地 做展示
// 页面上数据 需要发生改变 一般存储在组件状态中 state
state = { result: [] };
// 挂在时触发
componentDidMount() {
// 发送请求
const url = "https://api.apiopen.top/getWangYiNews";
// 发送请求 JS代码 早期xmlHttpRequest
// 新的webAPI 建议使用Fetch
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
this.setState({ result: data.result });
});
}
render() {
return (
<FlatList
data={this.state.result}
keyExtractor={(item, index) => index}
renderItem={this._renderItem}
// 列数
numColumns={2}
// 每行的样式 多列布局使用
columnWrapperStyle={{
justifyContent: "space-evenly",
marginTop: rpx(10),
}}
></FlatList>
);
}
_renderItem = ({ item }) => (
// 计算每个元素的宽度 rpx
// 每个块填充10 两个边 总共4个
// 750-10*4 710/2
// 355
<View style={{ width: rpx(355) }}>
<Image
source={{ uri: item.image }}
style={{ width: "100%", height: rpx(200), borderRadius: rpx(10) }}
></Image>
<Text style={{ fontSize: rpx(30), padding: rpx(10) }}>{item.title}</Text>
</View>
);
}
# 实现滚动效果
// rnc
/***
* 横向滚动展示 通过FlatList实现轮播效果
*
*
*/
import React, { Component } from "react";
import { Text, View, Dimensions, FlatList, Image } from "react-native";
// rpx计算
const rpx = (x) => (Dimensions.get("window").width / 750) * x;
export default class App extends Component {
banners = [
"http://www.codeboy.com:9999/img/index/banner1.png",
"http://www.codeboy.com:9999/img/index/banner2.png",
"http://www.codeboy.com:9999/img/index/banner3.png",
"http://www.codeboy.com:9999/img/index/banner4.png",
];
// 挂在时触发
componentDidMount() {
// 启动一个定时器 实现自动滚动
setInterval(() => {
// 下一张
this.current++;
// 判断 越界情况 重置为0
if (this.current == this.banners.length) this.current = 0;
// 调用FlatList的滚动到指定位置方法
this.f1.scrollToIndex({ index: this.current });
}, 2500);
}
render() {
return (
<FlatList
data={this.banners}
keyExtractor={(item, index) => index}
renderItem={this._renderItem}
// 横向排列
horizontal
// 按页滚动
pagingEnabled
// 监控滚动
onScroll={this._onScroll}
// ref绑定 需要调用FlatList提供到的方法
// 当前组件类中 this.f1就代表FlatList组件对象
ref={(el) => (this.f1 = el)}
></FlatList>
);
}
// 默认滚动第一个 下标为0
current = 0;
_onScroll = (e) => {
// RN的事件经过特殊处理,如果打印需要先执行persist
// e.persist();
// console.log(e.nativeEvent.contentOffset.x);
// 滚动过程中 滚动的距离 偏移量
const offset_x = e.nativeEvent.contentOffset.x;
// 滚动的位置的宽度
const w = e.nativeEvent.layoutMeasurement.width;
// 滚动的偏移量/滚动位置的宽度 计算出滚动的是第几个 第几页
// console.log(offset_x / w);
// 后续滚动 是按页滚动的 取整
this.current = Math.round(offset_x / w);
};
_renderItem = ({ item }) => (
<Image
source={{ uri: item }}
style={{ width: rpx(750), height: rpx(300) }}
></Image>
);
}
# ReactNative 路由
react 提供了 router 路由 roact-router-dom
ReactNative 为了能够在移动端有更好的使用体验,专门开发了针对 RN 的路由系统。
官方网址:https://reactnavigation.org/
① 生成几个测试页面
在页面中写入基础结构和识别内容,一定要生成组件结构,否则路由切换报错
import React, { Component } from "react";
import { Text, View } from "react-native";
export default class APage extends Component {
render() {
return (
<View>
<Text style={{ fontSize: 30 }}> A页面 </Text>
</View>
);
}
}
App.js
// In App.js in a new project
import * as React from "react";
import { View, Text } from "react-native";
// 路由容器
import { NavigationContainer } from "@react-navigation/native";
// 栈式导航
import { createNativeStackNavigator } from "@react-navigation/native-stack";
// 1.引入页面组件
import APage from "./pages/APage";
import BPage from "./pages/BPage";
import CPage from "./pages/CPage";
import DPage from "./pages/DPage";
// 函数组件
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Home Screen</Text>
</View>
);
}
// 创建一个栈式导航
const Stack = createNativeStackNavigator();
// 应用根组件
function App() {
return (
// 最外层包裹一个路由容器
<NavigationContainer>
{/* 栈式导航 */}
<Stack.Navigator>
{/* 组件和路由名称的匹配对应关系 */}
{/* name 路由名称 */}
{/* component 组件名称 */}
{/* 默认加载第一个Stack.Screen */}
{/* <Stack.Screen name="Home" component={HomeScreen} /> */}
{/* options 可定制属性 */}
{/* 参数说明文档:https://reactnavigation.org/docs/native-stack-navigator#options */}
<Stack.Screen
name="A"
component={APage}
options={{
headerTitle: "首页",
headerStyle: { backgroundColor: "orangered" },
headerTitleStyle: { color: "white" },
}}
></Stack.Screen>
<Stack.Screen name="B" component={BPage}></Stack.Screen>
<Stack.Screen name="C" component={CPage}></Stack.Screen>
<Stack.Screen name="D" component={DPage}></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
pages\APage.js
import React, { Component } from "react";
import { Text, View, TouchableOpacity, StyleSheet } from "react-native";
/**
* 路由会向组件props注入两个属性
* 1.navigation 包含操作路由的方法 跳转方法
* 2.route 包含了路由的相关参数
*
*/
export default class APage extends Component {
render() {
// 解构出跳转方法
const { push } = this.props.navigation;
// push('路由名称')
return (
<View style={{ justifyContent: "center", alignItems: "center" }}>
<TouchableOpacity
activeOpacity={0.7}
style={ss.btn}
onPress={() => push("B")}
>
<Text style={{ fontSize: 30, color: "white" }}>跳转到B页面</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
style={ss.btn}
onPress={() => push("C")}
>
<Text style={{ fontSize: 30, color: "white" }}>跳转到C页面</Text>
</TouchableOpacity>
{/* 参数传递 通过push方法的第二个参数 */}
<TouchableOpacity
activeOpacity={0.7}
style={ss.btn}
onPress={() =>
push("D", { name: "JavaScript", msg: "JavaScript是React的基础" })
}
>
<Text style={{ fontSize: 30, color: "white" }}>跳转到D页面</Text>
</TouchableOpacity>
</View>
);
}
}
const ss = StyleSheet.create({
btn: {
backgroundColor: "#007acc",
padding: 10,
marginTop: 10,
},
});
pages\DPage.js
import React, { Component } from "react";
import { Text, View } from "react-native";
export default class DPage extends Component {
// 组件挂在时
componentDidMount() {
// 获取路由参数
// console.log(this.props);
// const {name, msg} = this.props.route.params;
// console.log('name:',name);
// console.log('msg:',msg);
}
render() {
const { name, msg } = this.props.route.params;
return (
<View>
<Text style={{ fontSize: 30 }}> D页面 </Text>
<Text style={{ fontSize: 30 }}>name:{name}</Text>
<Text style={{ fontSize: 30 }}>msg:{msg}</Text>
</View>
);
}
}
# 学子商城 App 开发
准备项目包并和模拟器关联,使用之前网盘共享的项目和 APK 即可。
登录页面 LoginPage.js
管理主界面 MainPage.js
商品列表页面 ProductsPage.js
商品详情页面 DetailPage.js
从FTP获取图片资源存储到项目包/assets下
# 路由模块
① 创建页面
创建文件,并在文件中通过rnc代码提示生成结构代码
,写标识信息
② 根组件配置路由
import * as React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
// 导入页面
import Login from "./pages/LoginPage";
import Main from "./pages/MainPage";
import Products from "./pages/ProductsPage";
import Detail from "./pages/DetailPage";
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
{/* name 路由名称 componet 组件 options 配置项 titlte 头部导航栏的标题文字*/}
<Stack.Screen
name="Login"
component={Login}
options={{ title: "管理员登录" }}
></Stack.Screen>
<Stack.Screen
name="Main"
component={Main}
options={{ title: "管理主菜单" }}
></Stack.Screen>
<Stack.Screen
name="Products"
component={Products}
options={{ title: "商品列表" }}
></Stack.Screen>
<Stack.Screen
name="Detail"
component={Detail}
options={{ title: "商品详情" }}
></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
# 登录页面
分析:
登录功能
实现
① 点击按钮后,触发事件处理方法
② 处理方法 把用户名和密码 发送请求到服务端接口
③ 服务端检验用户名和密码是否正确,返回给客户端。
- 校验成功 跳转路由页面到主菜单页面
- 校验失败 提示用户名或者密码错误
对于登录功能,前端需要做:
- 传递参数[用户名,密码] 发送给服务端
- 对于服务端校验后[用户名和密码是否正确]的结果进行处理和提示
拿用户名唯一的,查看到这条数据 数据中包含密码字段的值,密码字段的值是经过加密的,需要拿用户输入的密码加密后,进行对比存储的加密字段。
pages\LoginPage.js
import React, { Component } from "react";
import {
Text,
View,
Dimensions,
TextInput,
TouchableOpacity,
Image,
StyleSheet,
} from "react-native";
// rpx
const rpx = (x) => (Dimensions.get("window").width / 750) * x;
export default class LoginPage extends Component {
// 初始化用户名uname 密码upwd
state = { uname: "", upwd: "" };
// 登录处理方法
_doLogin = () => {
// 获取用户名和密码
const { uname, upwd } = this.state;
// 登录地址
const url = "http://www.codeboy.com:9999/data/user/login.php";
// 参数拼接
// 测试账号 uname=doudou upwd=123456
const body = `uname=${uname}&upwd=${upwd}`;
// POST请求
// fetch的参数
// 第一个参数接口地址
// 第二个参数
// method 请求方法 方式 POST
// headers 头信息
// body 参数结构
fetch(url, {
method: "POST",
headers: { "Content-type": "application/x-www-form-urlencoded" },
body,
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if (res.code == 200) {
// alert('登录成功')
// 跳转到管理主菜单页面
this.props.navigation.push("Main");
} else {
alert("用户名或者密码错误");
}
});
};
render() {
// 解构state
const { uname, upwd } = this.state;
return (
<View style={{ padding: rpx(80) }}>
{/* 用户名 */}
<TextInput
style={ss.input}
placeholder="请输入管理员用户名"
value={uname}
onChangeText={(uname) => this.setState({ uname })}
></TextInput>
{/* 密码 */}
<TextInput
secureTextEntry
style={ss.input}
placeholder="请输入用户登录密码"
value={upwd}
onChangeText={(upwd) => this.setState({ upwd })}
></TextInput>
{/* 登录按钮 */}
<TouchableOpacity
activeOpacity={0.7}
style={{
backgroundColor: "#1390f7",
paddingVertical: rpx(10),
borderRadius: rpx(10),
marginTop: rpx(50),
alignItems: "center",
}}
// 绑定点击事件 触发登录方法
onPress={this._doLogin}
>
<Text
style={{
fontSize: rpx(40),
letterSpacing: rpx(20),
color: "white",
}}
>
登录
</Text>
</TouchableOpacity>
{/* logo 文字 */}
<View
style={{
marginTop: rpx(100),
flexDirection: "row",
justifyContent: "space-evenly",
}}
>
<Image source={require("../assets/logo.png")}></Image>
<Text style={{ fontSize: rpx(40), color: "#9598a6" }}>
后台管理系统
</Text>
</View>
{/* 底部版权信息 */}
<Text
style={{
marginTop: rpx(100),
textAlign: "center",
fontSize: rpx(30),
color: "#9598a6",
}}
>
©2017 版权所有,CODEBOY.COM
</Text>
</View>
);
}
}
const ss = StyleSheet.create({
input: {
borderBottomWidth: 1,
fontSize: rpx(40),
color: "black",
padding: rpx(15),
},
});
# 主菜单界面
- 页面导航标题 右侧有一个小图标
- 统计数据 真实情况数据是接口返回 这里需要模拟数据
- 按钮导航 导航的名称图片和跳转路由地址
- 统计数据和按钮导航的结构布局,是使用 FlatList 高性能的列表组件
pages\MainPage.js
import React, { Component } from "react";
import {
Text,
View,
Dimensions,
StyleSheet,
Image,
TouchableOpacity,
FlatList,
} from "react-native";
// rpx
const rpx = (x) => (Dimensions.get("window").width / 750) * x;
export default class MainPage extends Component {
data = [
// 模拟统计数据
{
title: "上架商品总数",
data: "24380",
desc: "+128%较上月",
color: "blue",
},
{ title: "注册用户总数", data: "1965", desc: "-50%较上月", color: "blue" },
{ title: "下架商品总数", data: "24380", desc: "+128%较上月", color: "red" },
{ title: "当日PC端PV量", data: "14281", desc: "-50%较昨日", color: "red" },
{ title: "移动端PV量", data: "29315", desc: "-34%较昨日", color: "blue" },
{ title: "App总下载量", data: "7422", desc: "+18%较上周", color: "red" },
// 导航按钮数据
{
title: "商品管理",
icon: require("../assets/menu_product.jpg"),
target: "Products",
},
{
title: "用户管理",
icon: require("../assets/menu_user.jpg"),
target: "Products",
},
{
title: "订单管理",
icon: require("../assets/menu_order.jpg"),
target: "Products",
},
{
title: "首页管理",
icon: require("../assets/menu_refresh.jpg"),
target: "Main",
},
];
// 组件挂在时
componentDidMount() {
// 解构出配置项 react-navigation reactNative路由 提供了头部导航 及其设置方式
const { setOptions } = this.props.navigation;
setOptions({
// 右侧图标 函数调用返回的是一个JSX结构
headerRight: () => (
<TouchableOpacity activeOpacity={0.7}>
<Image
source={require("../assets/user.png")}
style={{
width: rpx(50),
height: rpx(50),
borderRadius: rpx(25),
}}
></Image>
</TouchableOpacity>
),
});
}
render() {
return (
<View>
<FlatList
data={this.data}
renderItem={this._renderItem}
keyExtractor={(item, index) => index}
numColumns={2}
></FlatList>
</View>
);
}
_renderItem = ({ item, index }) => {
// 模拟数据data
// 统计数据
if (index < 6) {
return (
<View style={ss.cell}>
<Text style={{ fontSize: rpx(30), color: "#666666" }}>
{item.title}
</Text>
<Text
style={{ color: item.color, fontSize: rpx(30), fontWeight: "bold" }}
>
{item.data}
</Text>
<Text style={{ fontSize: rpx(28), color: "#666666" }}>
{item.desc}
</Text>
</View>
);
}
// 按钮导航数据
return (
<View
style={{
width: rpx(375),
alignItems: "center",
paddingVertical: rpx(30),
backgroundColor: "white",
}}
>
<TouchableOpacity
activeOpacity={0.7}
// 事件触发的方法不能写() 写括号就立即触发了
// 使用箭头函数中转以下
onPress={() => this.props.navigation.push(item.target)}
>
<Image
source={item.icon}
style={{ width: rpx(120), height: rpx(120) }}
></Image>
<Text style={{ fontSize: rpx(30), textAlign: "center" }}>
{item.title}
</Text>
</TouchableOpacity>
</View>
);
};
}
const ss = StyleSheet.create({
cell: {
// 750rpx 分两半 一半是375
width: rpx(375),
borderBottomWidth: 1,
borderRightWidth: 1,
borderColor: "#666666",
alignItems: "center",
paddingVertical: rpx(20),
backgroundColor: "white",
},
});
# 商品列表页
分析:
- FlatList 列表布局
- 触底加载
- 下拉刷新
- 回到顶部
import React, { Component } from "react";
import {
Text,
View,
FlatList,
Dimensions,
Image,
TouchableOpacity,
} from "react-native";
// rpx rpx(30)代表30rpx
const rpx = (x) => (Dimensions.get("window").width / 750) * x;
export default class ProductsPage extends Component {
// 发请求 拿数据 存本地 做展示
state = { res: null };
url(pno = 1) {
return "http://www.codeboy.com:9999/data/product/list.php?pno=" + pno;
}
componentDidMount() {
fetch(this.url())
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ res });
});
}
render() {
// 判断res接口数据未返回时 不渲染 防止报错
if (this.state.res == null) return <View></View>;
return (
<View style={{ backgroundColor: "white" }}>
<FlatList
data={this.state.res.data}
keyExtractor={(item, index) => index}
renderItem={this._renderItem}
ItemSeparatorComponent={this._ItemSeparatorComponent}
onEndReached={this._onEndReached}
onEndReachedThreshold={0.1}
></FlatList>
</View>
);
}
// 触底加载数据
_onEndReached = () => {
// 解构获取 当前页 最大页
const { pno, pageCount, data } = this.state.res;
if (pno == pageCount) return;
fetch(this.url(pno + 1))
.then((res) => res.json())
.then((res) => {
console.log(res);
// 数据拼接 拼数组
res.data = [...data, ...res.data];
this.setState({ res });
});
};
// 分隔组件
_ItemSeparatorComponent = () => (
<View style={{ height: 1, backgroundColor: "#666666" }}></View>
);
_renderItem = ({ item }) => (
<TouchableOpacity
activeOpacity={0.7}
style={{ flexDirection: "row", padding: rpx(20) }}
>
<Image
source={{ uri: "http://www.codeboy.com:9999/" + item.pic }}
style={{ width: rpx(200), height: rpx(200) }}
></Image>
<View style={{ flex: 1, justifyContent: "space-between" }}>
<Text numberOfLines={2} style={{ fontSize: rpx(35) }}>
{item.title}
</Text>
<Text style={{ fontSize: rpx(38), fontWeight: "bold", color: "red" }}>
¥{item.price}
</Text>
</View>
</TouchableOpacity>
);
}