开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术:移动端APP5】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/76/detail/15783
移动端APP5
内容介绍:
一、WEB移动端-REACT NATIVE示例
二、工程实例
一、WEB移动端-REACT NATIVE示例
两边用的是同一个web后台,可以看见详情然后返回可以进行一些搜索。接下来讲解是如何实现的。
首先开发要安装相关的东西,即安装过程和官网给的一样。
开发环境搭建
ISO
开发平台:macOS
Node、Watchman、Windows
Node、Python2(windows)、Watchman(macOS)、JDK、Android Studio
Reference:https://reactnative.cn/docs/getting-started/
Hooks at a Glance(预览)
-https://reactis.org/docs/hooks-overview.html
Hooks API Reference (详情)
-https://reactis.org/docs/hooks-referencehtml#usestate
Hooks are a new addition in React 16.8.
-They letyou use state and other React features without writinga class.
API是React里面的,不是React native里的,React native像是React的一个子集。
有些react在写的时候没必要写成类,在react里面可以写class也可以写function。如果不想写class只想写function,可以用Hooks体现react的一些特性。
例子:
import Reac,{useState
}
from ‘react'
function Example()
{
//Declare a new state variable,whichwell call"count”
const
{
count,setCount
}
= useState;(0)
return(
You clickedfcount)times
setCount(count+1))
}>
Click me
)
;
}
大前提写的是类,可以去用这个function,其中使用的useState是Hook的一种,这个方法会返回两个东西,一个是状态参数指的是状态的初值,另一个是设置这个状态的方法,写useState的含义是希望在函数里有count这个状态,对应有一个方法setCount,需要这个是因为在react里面只有通过这种方法设置才能在react里面做更新,如果直接Count=多少是不行的。useState是一个状态函数,会返回一个状态和设置状态的方法。
没有去定义别的东西就可以直接使用count,接下来你已经点击多次了,下面有一个按钮,这个按钮在点击的时候执行的就是setCount,在setCount的时候将count的值加一,因为setcount就是对count做处理,它的效果就是count三维值加一,又因为状态发生了变化,所以react拦截之后就要刷新一下页面,所以每次点击这个按钮的时候就会发现上面count的值在不断的增加,定义好这样的example之后就可以在其他地方复用了,这种useStata是在定义一个属性,如果里面有多个属性就多次调用useState。如下:
function ExamplewithManyStates(){
//Declaremultiple state variables
!
const
{
age,setAge
}=
useState
(
42) //age
是一个初值为4
2
的属性
const{fruit,setFruit}=useState(‘ banana’)
;
fruit
是初值为b
anana
的属性
const
{
todos.setTodos=useState(
[{
text’Learn Hooks’}]
;
/
/…
}
hooks函数实际上就是让你有能力把react里面的状态,生命周期这样的特征,在函数写的构件里面将它写进去,即让react去实现。因为没有写类,即没有在类的里面工作就不用去写类。常用的除了usestate,还有useEffect.
useEffect在页面发生变化,例如整个页面是一个文档模型,只要状态发生变化要更改状态的时候,useEffect方法就会被调用,它同样也不用写在类里面就在函数里面。
代码如下:
i
mport React,(useState,useEffect
)f
rom’react’;
f
unction Example(){
const [count,setCount]=useState{0};//
定义属性/
/
//Similar to componentDidMount and compoentDidUpdate
u
seEffect{(
)=
>{
//Update the document title using the browser API
d
ocument title ‘You clicked {count}times’;
}
)
r
eturn{
<
div>
You clicked {count}times
<
button onClick={()=>setCount{count=1}}
//每次点击都需要count加一,但如果也想要在网页最上面的title上显示用useEffect。
点击按钮之后,count值的属性就会发生变化,值发生变化就需要刷新,此时就会调用useEffect,与compoentDidUpdate是一样的,所以每当页面上只有状态发生变化,提交刷新后就会调用useEffect,这里我们将document title更改了,因此在title上能看到更改后的值。除了点击它状态会发生变化,还有页面加载进来之后example这个函数被调用的时候,会去调用compoentDidUpdate,本质上也是状态发生变化,useEffect也会被调用,所以能看到页面一加载useEffect被调用,这个是example被创建出来之后才被调用的,此时useState已经执行完了,所以会显示已经改进0次,之后每次点击click,count状态发生变化都会触发useEffect被调用,就不断地去刷新就会出来。
Click me
<
/button>
<
/div>
)
;
}
react调用effects方法在每一次更新完之后,包括第一次被呈现。
此外还有
useContext lets you subscribe to React context without introducing nesting:
在跑这个应用的时候会有一个上下文环境,就会想把这个取出来。
useReducer lets you managelocal1state of complex components with a reducer:
相当于当你的状态比较复杂需要处理的时候的一个处理机制。
例子:
先定义一个主题,一共分为两种,一个是light,前景色是白色背景色是发黑的颜色。另一种是dark,前景色为发黑背景色为白色。相当于定义了前景色和背景色的两种不同风格的主题。
c
onst thems={
light{
foreground:”#000000”’
background:’#eeeeee”
}
,
dark
{
foreground:”#ffffff”,
background:”#222222”
}
;
创建上下文环境,包含主题和前景色背景色。
const ThemeContest React createcontext(themes.light);
APP这个函数,它会返回一个主题,它的值为dark,在里面加了一个Toolibr。
f
unction App(){
return(
<
/Themecontext.Provider>
)
;
}
Toolibar结构如下所示:Toolibar里面做一个div,显示一个带主题的按钮,
f
unction Toolibar(props){
return(
);
}
f
unction ThemedButton(){
const theme=useContext(ThemeContext);
按钮显示前景色和背景色用什么,在带主题的ThemeButton里面通过useContext获取ThemeContext对象。
只要拿到这个对象,就用这个对象的前景色和背景色作为这个按钮的前景色和背景色画出来,一步步往前导,这个Theme是从Context里拿到的,ThemeContext当前没有往上寻找,Toolbar里面没有继续往上找就能找到ThemeContext,找到它的提供者可知它的值用的是theme.dark,初始化定义的时候用的是light。所以它用的是dark的配置绘制出button。
return(
color
theme.foreground}}>
I
am styled by theme context
);
}
实际上,抽象来看Themes里面就是一组键值对,更抽象来看其实就是JSON一堆配置信息,这些配置信息会被使用contest的东西拿到,因为配置信息被扔到了context里面,所以只要context通过themes对象里面的内容一层层传递下去,下面再获取它的上下文环境,剩下的就是在配置信息里取里面的内容,所以Themes就可以理解为配置信息,它可以是key:Value也可以是JSON。不管是那一种在拿到后都可以传递给一些构建,这些构建通过useContext就可以获取到里面的信息。
R
educer Hook
const initialState={count:0};
f
unction reducer(state,action){
switch(action.type){
case’increment’
return{count state count+1};
case’decrement’;
return{count state.count-1};
d
efault
throw new Error();
}
}
使用reducer需要传递两个参数,第一个参数是在进行处理的时候处理的方法,第二个为它的初始状态。在下方有两个button,这两个button都需要调用dispatch,dispatch是当用某一个reducer去处理某个状态,这个状态初始值是它的时候,会返回两个对象。一个是对象是拿初始状态初始化,
function Counter(){
const{state,dispatch}=useReducer{reducer,initialState}另外一个是拿着个函数记住dispatch,相当于得到了函数的引用。底下在调用dispatch的时候就知道是调用这种函数,调用的时候需要传递参数,type=’decrement’,这是一个键值对当中的值。
r
eturn(
<
>
Count;{state.count}
dispatch({type:’decrement’})}>-
dispatch({type:’increment’})}>+
);
}
然后回到前面,首先过去的action就是decrement,所以在action的type上可以看当时传递过来的type是什么。这相当于action是一个参数,参数里面放一个type,就看action的type这个参数到底是什么。要处理的就是initialState这个状态,对state进行处理,初始状态为0,dispatch调用decrement过前面去,调用过去后就把count的值重新赋予了,赋成当前count减1,因为初始状态赋给了state,state里面又有count存在,赋值为0。
所以useReducer就是当你想把处理这个状态比较复杂的一个逻辑的方法和这个状态去绑定,然后在调用这个方法的时候可以传递一些参数,这个方法可以拿到这个参数后去决定根据这个参数执行不同的逻辑。
下面的第二个按钮按一下就能调用dispatch,但是是和reduce绑定的因此是调用在reduce上。它的类型是increment,到前面抽取type的值为increment,把count的值设成了count:state.count+1。这里面的count需要从state中拿,reduce和initialState调用的useReducer会把状态取个名字,就是自己设定的state,所以reduce知道它处理的是哪个state。前面的state就是从这里来的。所以上面可以操作reduce里面的count。
reduce的意思就是要把自定义的一个状态的比较复杂的操作不仅只是设置一个值,而是要根据它传递进来的参数来决定要执行什么样的操作,把一个复杂的逻辑如何绑定下来。
State、Effect、Context、Reducer是最常见的四类Hook函数,但实际上真正的Hook API要更多,甚至可以自定义一个。总的来说并不复杂,它基本是在代替原来写类的时候的方法,现在希望的是函数写的过程中能够自动生成。
二、工程实例
工程简介
应用:图书浏览App
框架:React Native
功能/界面:登录、图书列表、图书详情、搜索
运行此工程的注意事项:
1、必须结合bookstore_backend服务端的启动,并参考https://reactnative.cn/docs/network/对相应的模拟器或硬件设备进行配置。
2、若bookstore_backend服务端和此工程在本地启动,必须将urlconfigjs中的apiUrl修改为本地的ip地址+bookstorebackend服务端的端口号。
原因是程序不是在当前环境中跑的,是加载在仿真器中跑的,仿真器中的IP地址不一样,所以必须写成当前这台机器的IP。
工程结构
components
自定义React Native组件
screens
React Native界面
android
安卓端工程目录
ios
iOS端工程目录
android和iSO如果没有特殊需求的话可以不需要往里面加东西,如果你的两个手机端表现出来的东西有一定差异要做一些定制化的东西就可以放到这两个里面去。
screens里面主要是工程界面,里面主要界面一个为home界面如下
它还有抽屉式的路由页面,但我们此次并没有做。抽屉式路由加上刚才的页面就是主页面。主页面除了抽屉式路由还嵌了列表。登录的时候就包括主页、列表书籍的详情页。
其他需要用的的工具类如Searcherbar它不是个完整的页,它做成了一个componment,componment放到一个目录里面可以避免被引用。
登录界面
输入正确的用户名、密码(如bookstorebackend工程中的用户名thunderboy,密码reins1409),点击“登录”控件,即可跳转至图书浏览界面。
两个页面显示会有一些差异,如两边的按钮不一样,这是因为写的有差异。在整个代码里它会做一个自适应,所以无论是字的大小还是输入框的宽度都会去自动适配。
登录界面——LoginScreen
e
xport function LoginScreen(){
const[name,setName]=useState(‘’);
const[password.setPassword]=useState(‘’); //useState
里设置了n
ame
和password两个state,分别对应的设置方法是s
etName
和set
password
。/
/
conset{signin}=React.useContext(AuthContext); //useContext
使用的是Authcontext,它需要使用signin函数//
return(
Login //
文本显示的是L
ongin,//
{/*
账号和密码*/}
style={styles.textinputStyle}
onChange Text={text=>setName(text)}
//当有文本发生变化的时候就要做调用setName函数的处理,setName用hook引入了一个变量,要拿到当前的输入的Text里面,用当前的Text设置内容。每当在输入框输入文本的时候,它就会将这个文本内容复制下来,每按下一个按钮只要在text里就会出现,所以内容会不断发生变化,它的最终值会跟name绑定,当你什么都不输入的时候会显示输入用户名。//
value={name}
placeholder={‘
请输入用户名‘}/
>
<
Textinput
s
tyle={styles.textinputStyle}
p
laceholder=’
请输入密码’
o
nChange Text={text=>setPassword(text)}
secureTextEntry={ture} //跟上面相比多了这句话,只有有这句话,刚才看到的输入用户名和密码的时候它才可能是会出现圆点。//
v
alue={password}
p
assword={ture}/>
{
/*
登录*/
}
<
Button style={styles.longinaBtnStyle}title=”
登录”
//
登录按钮/
/
onPress={()=>{
fetchData({name,password,signin});
}
}>
登录<
/Text>
{
/*
设置*
/}
<
View style={styles.settingStyle}>
忘记密码<
/Text>
注册<
/Text>
<
/View>
<
/View>
)
;
}
fetchData能做什么用处
fetchData对URL发送一个POST请求,POST意味着需要在body里写参数,即usename传递的name,和password传递的password。在headers里写json。
LOGIN_URL=apiUrl+”/login”;
f
unction fetchData({name,password,signIn}){
fetch(LOGIN_URL,init:{
method:’POST’,
credentials:’include’,
headers:{
‘context-Type’:’application/json’,
},
body:JSON,stringify(value:{
“username”:name,
“password”,:password,
}),
})
//到此会被发走,执行完会有response,response底下的部分是有关reactNangation,它的本质是希望在本地添加一个新的项。
.
then((reponse
“”“'”)
=>{
//let cookie=response._bodyBlob._data.blobld;
//console.log(“cookie in login screen:”+JSON.stringify(response));
let_storeData=async()=>{
try{
await AsyncStorage.setltem(@Bookstore:token,’exist’);//表示新建的Token已经有了。
}catch (error){
//Error saving data
}
};
_storeData();
return response.json();
})//response返回的是json,则取里面的json返回然后到then。然后就拿到了responseData里面的json对象。
.
then((responseData)=>{
//注意,这里使用了this关键字,
//为了保证this在调用时仍然指向当前组件,
//我们需要对其进行“绑定”操作
c
onsole.log(responseData);
i
sSuccess=(responseData.status==0?ture:false);
//是0表示成功登录了,否则密码错误,如果成功登录,调用signin方法,否则会在本地弹出用户名或密码错误的信息出来。
i
f(isSuccess){
signin()
}
else{
Alert.atert(“
用户名或密码错误!”);
}
})
.
catch({error)=>{
console.error(error);
}
);
}
图书列表界面(主界面)
登进去后可以看见主页面。上下滑动可以浏览图书,点击任一栏可以跳转至相关的图书详情界面。从左边向右滑动,可以看到抽展导航栏的出现。
主页面——HomeScreen
HomeScreen
主要分为两部分,首先进去后,手机上的APP就要开始做页面的跳转导航之类的,所以要创建StackNavigator,它将导航的信息加进去,底下用到BookListAndDetall函数,这个函数需要将导航的信息填进去。
const Stack=createStackNavigator()
;
function BookListAndDetall
(
){
return(
//保证在安全可视区域内泊染,避免泊染内容被如i0S11的“刘海”遮盖。
//对Stack进行操作,Stack的页面有BookList,另一个是Detail。未来如果要跳到BookList或者Detail去的话就得跳转到BookScreen这个页面。然后可以跳转到BookListScreen里面去。
)
;
}
function MyCrscreen({navigation}) {…}
function MyorderSrcen({navigation}){…}
function MyProfileScreen
({navigation}){…}
const Drawer = createDrawerNavigator();
export function HomeScreen(){
return(
)
;
}
完整代码包括很多函数包括我的订单、我的个人信息等等全是显示很简单的信息。底下为真的HomeScreen有一个导航栏,导航栏中初始页面是Home,是当前页面,它有几个导航栏和大家在web页面上看到的是一样的,就是把抽屉式的导航栏加载在那里,这即是Homescreen做的事情,其中BooklistAndDetal就是有BookList也有Detail。BookList是要先显示出来,所以刚到页面首先跳转的是BookList,然后页面会跳转到BookListScreen里面去。
e
xport function HomeScreen(){
return (
/>
/Drawer.Navigator>
)
;
}
抽屉导航栏
点击任一栏可以跳转到相应的界面。
随便点击其中一个,例如点击MyCart就会跳到iphone手机的页面。
图书列表界面——BookListScreen
c
onst GETBOOKS_URL=apiUrl+”/getBooks”,;//
先拿到书
e
xport class BookListScreen extends React.Component{
constructor(props){
super(prpos);
t
his.state={
books;[],
showBooks:[]
isLoading:ture,
}
}//初始化的时候状态都是空的,要把书给拿到,当有人点了那本书就会跳到detail页面,导航到Books里面去,
c
omponentDidMount(){…}
f
etchData(){…})
g
etText(data){…})
n
avigateToDetail({item}){
this.props.navigation.push(“Detail”,{detail:item});
//
跳转到图书详情页面
}
renderBook={{item}}=>{…}//呈现所有的书,底下呈现每一个表,先把每一个表呈现出来然后呈现每一本书。呈现列表的方式是先由页面状态判断是不是初始值,是正在加载还是已经加载,如果是正在加载,把空的页面拉出来就可以了,如果已经加载完,保证在安全可视区域内演染
c
ance(){…}
r
ender(){
if(this.state.isLoading){
return(
)
)
r
eturn(
保证在安全可视区域内渲染,即不要出现被挡住的事情,然后显示SearchBar。
this.getText(data)}cancelSearching={()=>this.cancel()}/>
<FlatList//它不是二维带分组的例子而是平铺,平铺则需要给一个数据,这个数据是show.Books。它一开始为空就需要数据能够被抓到,将输出的信息呈现出来,呈现出来需要将每一项都拿到,每一项的key都是它的输入值,
data={this.state.showBooks}
renderitem={this.renderBooks}//对于每一项的呈现则调用renderBook的方法来实现,所以对每一项都调用了renderBook, 所以在data里每一项都会传递给renderBook。传的时候传递给了item。
style={styles.list}
keyExtractor={item=> item.bookled}
<
/safeAreaView>
)
;
}
}
图书列表每一栏的渲染
拿到item后点击TouchableHighlight,即所有可以点击的东西,一点就可以跳到详情将当前这本书传递出来,会跳到Detail这个页面传递进参数就是这本书,而Detail就是BookScreen,所以它跳到详情页的页面上去。
因此第一步则说明整个是TouchableHighlight,也就是说这里面的每一本书都在TouchableHighlight里,随便在任何一个地方点都可以,都会产生事件,这里面包含图片、书名、作者和价格。点击一下就会跳转页面到Detail去,把当前项的信息带过去,之后它会去查找这边的路径,Detail对应的是BookScreen。接下来就调用BookScreen。
renderBook=(
{
item})=>
//{item}是一种“解构”写法,请阅读ES2015语法的相关文档
//item也是FlatList中固定的参数名,请阅读FlatList的相关文档
return(
()=>{this.navigateToDetail({item;})>
source={{uri:item.image}}
style={styles.image}
/
>
}>
{item.name}/Text>
>{item.author}/Text>
{}
item.pricek
}
);
}
图书详情页面
点击左上方的返回键可以返回图书列表页面
Bookscreen的实现是由刚才的路由的参数Detail过来的render实现的,所以拿Detail的内容出来,拿出来后Details显示书的图片和书的名字以及目录信息。
e
xport class BookScreen extends React.Component{
render()
{
//console.log(this.props.route.params.detail);
let detail=this.props.route.params.detail;
r
eturn(
source={{uri:detail.image}}
style={styles.image}
/>
{detail.name}
作者:
{detail.author}
ISBD: {detail.isbn}
类型:
{detail.type}
单价:¥
{detail.price}
库存:
{detail.inventory}
<
View>
{detail.description}
<
View>
)
;
}
}
以上为我们所看到的四个页面之间的关系以及他们是怎么跳转的,因为这边定义的是堆叠式的所以当从列表页路由到详情页的时候是堆叠式的进入,当点击箭头返回的时候,会自然的弹会详情页里。
如果没看到四个页面,实际上有一个 Home,是堆叠式菜单加现在看到的BOOKscreen .