React Native
React Native是React.js下的一个框架,由Facebook团队于2015年开源,它允许开发者使用React语言开发安卓、IOS应用,与Windows桌面端应用,以及网站。
React Native顾名思义,就是用React写原生的东西(native即原生),不需要二次编译或是借助其他东西才能运行。比如说开发安卓手机APP,React Native能直接生成相应的安卓原生代码(Java、Kotlin),相当于你自己用Java或Kotlin开发APP。这不需要借助其他东西就能跟平常一样运作,像其他跨平台框架,如Tauri则需要借助WebView。
React Native在语言上虽然跟React很像,但还是有不少区别。比如说手机端必须用<View>
代替html语言的<div>
,见下图
Related
React Native UI库介绍与对比
React Native的生态与React.js相比,没那么丰富,或者说手机端的生态本身就跟Web端相差甚远。React.js作为Web端虽然生态丰富,但由于其JS库大多数都不能直接用在React native中,因此很多在Web端存在诸多解决方案的问题,如代码块高亮、渲染数学公式,在手机端都难找到合适的办法。React Native的UI库,其实可以选择的并不多,不像Web端百花齐放,选个UI库都能选择困难症。在本文,我将会介绍几个我所知道的React native UI库,其中有几个我是用过的。1. React Native Paper这是一个Material Design风格的UI库,在GitHub拥有13.4k个stars,官方文档👉React Native Paper。该库安装步骤简单,组件使用以及自定义也容易,唯一的缺点就是组件不够多,有些场景需要你另外补充其他库来满足需求。React Native Paper应该是React native唯一一个Material Design UI库,该库能够跟React native navigation整合,构建Material风格的底部导航栏(见下图)。如果你喜欢Material Design风格,那么React Native Paper绝对是不二之选。值得一提的是,安卓版的原生风格本身就是Material Design的。下面是React Native Paper的安装命令:npm install react-native-paper react-native-safe-area-context react-native-vector-icons更多安装细节请看Getting Started。2. Tamagui这是个能够同时在React与React Native使用的UI库,在GitHub拥有12.2k个stars,官方文档👉Tamagui。不推荐使用这个UI库,它是我写React native所用的第一个UI库,当时我被官方文档的UI所吸引,结果自己安装后运行才发现,UI风格得自己设置,官方的UI好像得花钱买。这还没完,Tamagui的theme设置非常麻烦与复杂,很多人表示自己看了半天都没搞懂文档在说什么,有些人甚至说不知道哪来这么多星。而Theme的设置是避免不了的,Tamagui相当于headless刚开始几乎没有任何UI风格,需要你自己设置自己的主题。加上Tamagui引入了过多的概念,如style还得考虑什么size token,总之最后我放弃了这个UI库。如果你想要体验这个UI库,由于安装过程比较长,请看Installation。3. React Native Elements这是个组件相当丰富的UI库,其在GitHub已经积累了25.3k个stars,官方文档👉React Native Elements。不过我没用过该库,主要原因是该库似乎已经过时了,没人维护,上一个release还停留在2023年。该库的作者目前把主要精力放在了另一个新的UI库——gluestack上了。4. GluestackGluestack也是一个可以同时在React跟React Native使用的UI库,可以看成是React Native Elements V2,这个库目前在GitHub积累了3.5k个stars,官方文档👉gluestack-ui。这个UI库的组件还算丰富,像BottomSheet和Actionsheet都有,不需要你额外安装其他React native库。5. Ant Design Mobile RN这是唯一一个国产的React Native UI库,喜欢antd风格的可以使用,官方文档👉Ant Design Mobile RN。Antd RN组件还行,至少比React native paper多,最主要的是有form组件,这对于有表单验证需要的人来说很友好。同时还有Carousel、ActionSheet、Modal,这就不需要你安装太多其他的库。6. RNUILibRNUILib也是一个组件相当丰富的UI库,并且它还提供了很多特殊组件,如键盘相关的组件,官方文档👉RNUILib。同时,它还是唯一一个有WheelPicker的UI库。目前RNUILib在GitHub已经积累了6.7k个stars。除了WheelPicker,RNUILib还提供了DateTimePicker、KeyboardAccessoryView等这些在React native难实现的组件,这些组件在其他UI库都是没有,但它们的需求却不少,这也是RNUILib一大特点。本文React native的UI库就介绍完了😄,不知道你喜欢哪个UI库呢?欢迎在评论区发表你的看法吧!😇
给Web开发者写的React Native简介,React Native与React的区别与对比(1)
React Native是React下的一个跨平台框架,能让你用熟悉的React JSX语法来进行跨平台开发。所谓的跨平台开发是如今的一种趋势,即用同一种语言来同时进行Web端、手机端安卓与IOS、桌面端Windows、MacOS、Linux的开发。这样不仅能极大的提高开发效率,同时大大增加了代码的可维护性,节省了大量的成本。然而React Native虽然带个React,用的也是JSX语言,却跟React有很多不一样的地方。因为React Native不仅面向网页端,还面向手机端APP,而React Native的代码会直接编译为native原生代码。在本文中,我将会列举说明几个React Native的不同之处。首先,在React Native中我们不能像React那样直接使用HTML语言,因为无论是Android还是IOS,都无法编译HTML语言。因此,我们需要使用React Native提供的组件。在React Native中,如果你想要写<div>,则需要换成<View>。View组件在Web端会被编译成<div>,而在Android和IOS则分别对应android.view和UIView。更多的,如果你想要写<p>或者<span>,那么你需要写<Text>。Text组件可以嵌套(nested),而两个Text在一起会跟p一样换行<View>
<Text>1</Text>
<Text>2</Text>
</View>需要注意的是,在Web端你可以在任意HTML标签里写字符串,如<div>Hello!</div>,但是在React Native你不能在Text外写任何字符串,不然会报错。有了<View>和<Text>(<div>和<p>)你很自然的想要修改他们的样式。在Web端React,你可以写CSS然后通过className来设置样式,或者直接写style来改变样式。而在React Native中,你只能通过style来设置样式,并且CSS属性还跟Web端有很多不同。而设置style的方式也跟Web端React有一些不同,倒是跟Vue挺像。首先跟React一样的是,你可以像className那样写style:<View style={styles.container}></View>,也可以直接写<View style={{ marginTop: 50 }}></View>。而第一种写法,你需要在函数体(functional component)外将style写下来,具体例子见下(来自官网):import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
const LotsOfStyles = () => {
return (
<View style={styles.container}>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigBlue}>just bigBlue</Text>
<Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
<Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 50,
},
bigBlue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
});
export default LotsOfStyles;是不是感觉跟Vue有一点像😂。说完style的语法,接下来就是CSS的属性了。在React Native中,View的display属性默认是flex,并且只有flex或者none可选,并没有Web端的block值。而flexDirection跟往常的一样,除了默认是column,跟Web端相反。因此,在React Native所有layout都是基于flex的,要么不显示(none),要么flex,这跟Web端有很大不同。默认flex也意味着,你想要居中任何东西会变得非常方便,如果你使用React Native版Tailwind CSS——NativeWind,那么你只需要加上className="items-center"。在React Native中,flex: 1将会变得尤为重要,一个screen中的第一个View往往不能全屏,这时就需要flex: 1,而有时字体溢出需要加上flex: 1才会正常。除了flex外,React Native中的margin与padding也与React不同,以margin为例,你不能这样写margin: 10 20,你只能这样写marginHorizontal: 20; marginVertical: 10,而其他属性像marginLeft这些跟原来一样。在某些情况下,我觉得这种写法比Web端还要方便。接着,React Native的边框属性border也跟React有些不同,你不能直接这样写border: 1 solid #ddd,你只能这样一个个列出来borderWidth: 1; borderStyle: 'solid'; borderColor: '#ddd'。而在React Native中position属性只有absolute, relative, static可选,这也意味着如果你想要实现sticky相关的layout,如sticky header、collapsible tabview等,会变得十分困难。而这在Web端,你只需要加个position: sticky或者加个static转到fixed的监听器就能做到。说完View,我们说Text。在React Native中,想要实现文本单行溢出省略或者多行溢出省略,比Web端简便得多。你只需要设置numberOfLines即可:<Text numberOfLines={1}>666666666666666666666666666666666666666666666666666</Text>
<Text numberOfLines={4}>999999999999999999999999999999999999999999999999999</Text>需要注意的是,这是唯一方法,你不能像Web端一样设置-webkit-line-clamp: 4或者text-overflow: ellipsis。由于篇幅过长,我将会在下一篇文章中继续本文的话题。
前端跨平台开发框架对比:Flutter vs Tauri vs React Native
传统移动端开发往往需要同时兼顾Android和IOS的开发,而桌面端开发又需要同时兼顾Windows、MacOS、Linux系统。如果你想要全平台覆盖,不仅意味着要同时维护多套完全不同的代码(极大提高了维护成本),并且代码和逻辑还可能不能复用,这意味着高昂的开发成本(极低开发效率),每个平台都得从零开始写。现在国内还多出个鸿蒙系统,这意味着你要同时开发和维护更多套代码,哪怕补贴钱,这成本也不是小企业能够负担得起的。于是,跨平台框架应运而生,Facebook开源的React Native,曾经是最流行的框架,不过近几年被Flutter超越。它不仅能让你使用React语言同时开发Android和IOS APP,甚至还能进行Windows桌面端开发。而谷歌开源的Flutter,是目前最流行的跨平台框架,略微领先React Native。它能让你使用dart语言开发移动端与桌面端应用。而Tauri则允许你使用任何前端框架进行全平台开发,不过也需要你懂得一些Rust语言。我们先从开发体验出发来对比这三个跨平台框架。首先,React Native能够让你完全用JSX语言来进行跨平台开发,这对于本身熟悉Web技术的Web开发者而言无疑是非常好的。这意味着他们不需要多余学习太多东西即可直接上手。不过React Native虽然前面带个React,却跟Web端React本身有很多不一样的地方(见 给Web开发者写的React Native简介,React Native与React的区别与对比(1)),因此同样需要开发者去适应。而Flutter对于Web开发者而言就没那么好上手了,它需要开发者掌握dart语言进行开发。dart语言跟JavaScript有很大不同,这会让Web开发者觉得“反人类”设计。不过dart的语法跟Java或者Swift有点像,对于Android或者IOS开发者而言,Flutter或许倒没那么难上手。最后Tauri可以说是对Web开发者最友好的跨平台框架了,它允许你使用任何前端框架进行开发。不过想要开发完整的应用,你免不了得懂一些Rust,毕竟Tauri其实就是个Rust GUI框架。而Rust对于新人而言,学起来可比学C++要难一些。接着从性能出发(不准确),React Native会将所有代码编译成原生组件,生成UI画面则是通过原生的渲染,性能上比较接近原生应用。而Flutter则不使用原生的渲染模式,它自己在不同平台使用不同方法实现画面的渲染,性能方面比React Native稍好。最后Tauri通过调用各个平台的WebView来渲染画面,底层使用Rust来进行WebView与OS系统的通信,性能似乎比Flutter和React Native差一些。更细节、更准确的对比网上已经有许多了,我就不过多评价了毕竟性能这方面我也不太懂。最后从生态出发,React Native许多功能的实现非常依赖于社区的第三方库,而有很多库已经没人维护了。Flutter据说也好不到哪里去,但根据我自己的搜索,像渲染HTML、富文本编辑器、渲染数学公式,以及微信、支付宝接口,这些功能Flutter的支持似乎比React Native的要好,故而从整体上而言,Flutter的生态比React Native的要好,这也意味着在React Native想要实现相同的功能,可能要耗费更多时间。而Tauri呢能够完全使用整个Web端的生态,前端方面自然不用多说,主要是Rust的生态还是太不成熟了,有些Rust库早已过时但也没人维护,这就导致有些Tauri的插件用不了,因此你可能会在Rust方面耗费大量时间。总之,移动端方面React Native、Flutter、Tauri各有优劣,对于熟悉Web技术的开发者而言,React Native可能是最好的选择,Tauri次之。但从整体出发,Flutter可能是最好的选择,React Native次之。
给Web开发者写的React Native简介,React Native与React的区别与对比(2)
本文我们继续之前的话题给Web开发者写的React Native简介,React Native与React的区别与对比(1),在上文中我们讲到在React Native想要写<p>或者<span>需要用Text组件。除了展示文本,还有一个很重要的东西就是展示图片。在React Native中你无法使用HTML的<img>,而要用React Native提供的Image组件。处理图片可以说是React Native中的一个难点,因为在React Native中无论是什么图片都需要你设置一个宽度和高度,见实例:import React from 'react';
import {Image} from 'react-native';
import {SafeAreaView, SafeAreaProvider} from 'react-native-safe-area-context';
const DisplayAnImage = () => (
<SafeAreaProvider>
<SafeAreaView style={styles.container}>
<Image
style={{ width: 50, height: 50 }}
source={require('@expo/snack-static/react-native-logo.png')}
/>
<Image
style={{ width: 50, height: 50 }}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
<Image
style={{ width: 66, height: 58 }}
source={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==',
}}
/>
</SafeAreaView>
</SafeAreaProvider>
);
export default DisplayAnImage;你不能像在Web端那样直接写个width: 100%就完事了,由于React Native并不会计算图片的高度,所以如果你没有给高度设置一个数值,那么图片将会不可见。因此,在React Native想要实现Web端里width: 100%; height: 100%的图片,就需要你动态计算每张图片的高度(宽度固定)。好在React Native提供了getSize() API,允许你得到每张图片的原始宽度和高度,你可以根据自己的需要用这个API来造轮子。图片除了宽度和高度两个重要属性,还有一个很重要的属性就是resizeMode,这个对应Web端CSS的object-fit。而跟Web端不同,resizeMode的取值并非跟object-fit一一对应,并且resizeMode的默认值是cover,即图片会自动裁剪以保持宽高比。除了使用React Native本身的Image组件,你也可以使用expo-image,或者react-native-fast-image。不过fast-image已经没有维护了,倒是有个fork这个库的有维护,有需要的可以自行寻找。说完Image,还有一个很重要的东西就是列表List了。在Web端只要你的内容超出了屏幕高度,那么滚动是很自然的事情。但是在React Native仅仅使用View组件是不能滚动的,内容超出屏幕高度的结果就是内容不可见,想要滚动就需要用到ScrollView。而ScrollView提供了大量的API,可以让你非常方便的设置需要跟滚动相关的东西,这跟Web端相比是一个优势。比如说实现无限滚动,在Web端你要么使用别的组件,要么自己实现,实现方法大多都比较复杂,不仅要监听滚动,还要考虑很多其他东西。而在React Native你只需要将相应的handler放到onEndReached这个prop里,当滚动到底部时就会自动执行一次你的handler。又比如监听滚动,在Web端你需要在页面挂载时添加监听器,然后在页面卸载时去掉监听器,而在React Native你只需要直接将handler放到onScroll这个prop里就可以了。见下面例子:const App = () => {
function load_more(){
console.log('end!')
}
function ScrollHandler(e){
console.log(e)
}
return (
<SafeAreaProvider>
<SafeAreaView style={styles.container} edges={['top']}>
<ScrollView style={styles.scrollView}
onEndReached={load_more}
onScroll={ScrollHandler}>
<Text style={styles.text}>
Long Text......
Long Text......
Long Text......
Long Text......
Long Text......
Long Text......
Long Text......
Long Text......
</Text>
</ScrollView>
</SafeAreaView>
</SafeAreaProvider>
);
}除此之外,你还可以设置水平滚动,甚至水平换页实现走马灯效果。ScrollView只是List最简单的一种方式,它会一次性渲染所有子节点,因此性能不好。这时,FlatList和SectionList就派上用场了,他们继承了ScrollView的几乎所有特征,并且性能更好。而FlatList是其中使用率最高的,它提供了numColumns属性,允许你轻松构建网格布局。而除了React Native本身提供的组件,对于大数据列表,你还可以使用第三方库FlashList或者RecyclerListView,来替代FlatList获得更好的性能。值得一提的是,FlashList除了是高性能列表,还提供了瀑布流布局的列表组件MasonryFlashList。说完列表,我们来讲一下React Native的触碰事件。在React Native你不能像在React Web端那样,随便一个地方都能设置onClick。并且React Native只有Press事件,你需要在特定的组件如Touchable、Pressable、Text、Button才能处理Press事件。其中Touchable组件分为TouchableHighlight、TouchableOpacity、TouchableWithoutFeedback,顾名思义他们分别对应触碰高亮、触碰改变透明度、触碰无反馈(触碰不改变样式)。而Press事件在Pressable和Text又分为Press和LongPress。<Pressable onPress={onPressFunction} onLongPress={onLongPressFunction}>
<Text>I'm pressable!</Text>
</Pressable>最后,想要写按钮和输入框,需要Button和TextInput组件,React Native的按钮自带样式比HTML的按钮要好看,并且安卓版点击按钮自带波纹效果。而React Native的输入框使用体验跟Web端的大体一样,就是提供props有些不同,这里就不多说了。本文到这里,已经将React Native跟React的主要区别过一遍了。由于篇幅问题,更多的内容以及细节就不详细展开了。之后我还会更更多React Native相关的文章,如果你对React Native感兴趣,欢迎在评论区交流讨论。
Flutter、Tauri、React Native、Android原生的四次开发经历,为何最后我选择了React Native?
Flutter、Tauri、React Native都是目前移动端流行的跨平台开发框架,并且他们还能胜任全平台开发。React Native是最早开源的跨平台框架,而Flutter紧跟其后,并且Flutter最近几年超越React Native成为当前世界上最流行的跨平台框架。Tauri则是最近几年诞生的新跨平台框架,跟其他框架显著不同的一点是,它允许你使用任何前端框架,即你能够自由使用整个Web生态进行跨平台开发。Flutter、Tauri、React Native、Android原生我都尝试过,接下来我说一下我分别使用他们的开发经历。首先,我第一个使用的跨平台框架是Tauri,当时Tauri V2.0已经发布,我看它能够使用Nuxt.js或者Next.js进行开发,觉得蛮不错的。毕竟我有两个网站,一个是Nuxt写的,另一个是Next写的,这样我就只需要在原有代码基础上修改一下就行了。于是很快我就栽跟头了,首先是Nuxt的桌面端应用,我在dev模式下,没有发现任何问题,$fetch请求也能正常发送。结果build以后,发现所有的请求都废了,全是404,将url改为完整url,结果就是跨域问题。之后我才明白,Tauri其实是一个Rust GUI框架,HTML、CSS、JavaScript都只是用来渲染画面罢了,想要发送HTTP请求还得通过Rust。于是,在Tauri官方文档中,我找到了HTTP请求的插件,最后才解决了这个问题。这还只是个小问题,最后真正劝退我的还是写移动端的时候。之后我找到了一个Web端的手机端UI库,于是打算从零开始写一个手机端APP。刚开始一切都没问题,开发起来就跟Web端我适配手机端时一样。直到写登录功能时,我才遇到一个新问题。在Web端我们可以直接使用cookie来实现登录功能,而在Tauri似乎并没有cookie这个概念,我得使用OAuth2实现登录功能,并将access token与refresh token存放到安全的地方。而这就是一切问题的起因,我查看了Tauri官方提供的插件,最后找到了两个能用来存储数据的插件——Store和Stronghold,而Stronghold就是专门用于存储敏感数据的。于是,我直接根据官网的步骤开始使用,结果最后运行的时候rust build报错了,怎么样都修复不了。谷歌上也找不到什么有用的信息,最后我不得已去GitHub提了一下issue,没人答复。于是我只能放弃Tauri,毕竟这么简单的功能都解决不了,谁知道后面有没有更难的。后来我也知道这是为什么了,就是Stronghold的一个依赖过时了,但没人替换掉,总之这个问题短期内是无法解决的。具体见Stronghold doesn't work在放弃Tauri后,接着,我使用了React Native来开发。React Native虽然使用的语言是React的,但是开发体验完全不一样。并且我刚开始使用了错误的UI库Tamagui上手(具体见React Native UI库介绍与对比),加上创建项目使用的template不同,总之刚开始用了不多久,我就放弃React Native了,转而尝试安卓原生和Flutter。说到安卓原生,我的开发时间很短,因为一开始用了没多久就把我劝退了😅。首先是很奇葩的需要将所有的string都写进一个xml文件里,这跟Web开发这么久所形成的习惯格格不入。接着就是布局组件这些,都得在xml文件里写,而函数调用那些就得在kt文件里写。接着就是实现一个ListView想不到也这么麻烦,最后根据网上找的资料,我才勉勉强强实现一个。最后把我劝退的,是在安卓原生发送HTTP请求,我对Java或者Kotlin本身就不熟悉,结果我发现想要发送一个简单的请求居然如此麻烦,对比React Native直接一个fetch搞定,安卓原生哪怕下了OkHttp也还是那么复杂。总之,最后我觉得按这个情况下去,开发完一个APP得猴年马月。于是,这时我第一次使用Flutter进行开发,虽然Flutter用的是dart语言,对熟悉JavaScript的Web开发者来说有些反人类设计,但其写法跟Java相似,并且跟安卓原生相比,已经简化很多了,接着还有一点就是它的官方文档十分齐全,还附有视频。刚开始用Flutter开发也没啥大问题,基本上都克服了。Flutter的状态管理相比于React Native能够使用Zustand还是复杂很多,不过发送HTTP请求还算简单跟fetch有得一比,比安卓原生简单不知道多少倍。而Authentication的实现比React native navigation的实现过程复杂一些,但最后也是顺利实现。目前看来,我应该可以使用Flutter一直开发下去。最后是什么劝退我使用Flutter呢?答案很简单,因为我无法使用Android emulator进行测试。前面我使用flutter都是直接用浏览器来测试的,也就是说我测试的是Web端,而Web端想要实现跨域很麻烦,反正我一直实现不了。毕竟我要开发的只是手机端,于是我直接打开安卓版,结果要么是报错,要么好不容易报错没了,结果Android emulator崩了。最后我花了不少时间,都解决不了Android emulator崩溃的问题,GitHub上也有不少人遇到这个问题,于是我只好放弃Flutter。最后我重新使用React Native进行开发,并且弃用Tamagui,改用React native paper以后,基本上也没遇到什么大问题。一路顺利推进下去,而偶尔也会遇到一些问题,需要花很多时间来解决。就这样前前后后花了三个月,弦圈APP终于在3月1号开发完成。
(✔已修复)弦圈APP下载附件功能存在问题,目前暂时无法修复。如若需要下载附件,请先用Web端
今天有粉丝反馈,弦圈APP里下载的附件并不能打开。接着我马上打开APP测试,发现文件确实是下载了,但是却找不到下载的文件,这也是当初测试的一个疏漏😢。不过令人沮丧的是,我目前找不到解决这个问题的办法😭。目前这个问题的原因已经查明,就是下载路径的问题。文件下载成功后所放的位置file:///data/user/0/com.sinering.manitori/files,在手机里是打不开的,里面的文件对于用户而言不可见。用户下载的文件相当于存到APP的数据里了,我目前也不清楚如何在手机访问这些文件,开发的时候可以通过Android Studio查看,但是这是真机。由于这是我第一次写APP,自己的技术水平有限,而手机端APP与Web端相比,同样的功能没那么好实现,复杂很多。目前这个问题,我暂时找不到解决办法,其实就是一行代码有问题需要修改。import { File, Paths } from "expo-file-system/next";
const new_file = new File(Paths.document, new_filename);就是上面这行代码里的Paths.document,想不到真机实际位置这么离谱,压根找不到。之后我会想办法修改一个合适的位置。最后APP更新没有Web端更新那么方便,因为我没有配置APP热更新(付费的),用的是冷更新,即每次更新都得下载新的安装包,因此挺麻烦的但未来这个问题会改善。2025/3/23 1:46 更新:原本我以为这个问题解决不了了,结果意外之喜,我还是找到解决方案了😁。刚刚所有APP下载方式都已更新,有下载附件需要的可自行下载更新(其实现在也没几个人下载APP用)。之后大家可以直接在手机APP端自由下载附件了!😇