news 2026/9/20 2:33:54

React Native Web 与 React Native Elements 跨端实战:从零构建 GitHub 仓库查看器 gitphone

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React Native Web 与 React Native Elements 跨端实战:从零构建 GitHub 仓库查看器 gitphone

React Native Web 与 React Native Elements 跨端实战:从零构建 GitHub 仓库查看器 gitphone

【免费下载链接】react-native-elementsCross-Platform React Native UI Toolkit项目地址: https://gitcode.com/gh_mirrors/re/react-native-elements

本文以 React Native Elements 官方博客《React Native Web》为基础,完整还原一个"一套代码同时运行在 iOS、Android 与 Web 三端"的真实案例:用create-react-native-web-app(CRNWA)初始化项目,接入 React Native Elements(RNE)组件库,配合react-router完成跨端路由,并通过 GitHub API 打造一个可查询任意仓库提交记录的gitphone应用。读完本文,你将掌握 RNE 与 React Native Web 的组合套路、webpack 转译与图标字体配置、跨平台路由的拆分方法,以及 HOC 组合、表单提交、API 数据渲染与边界处理的完整链路。

背景:为什么是 React Native Web + React Native Elements

React Native 的核心价值是"一次编写、多端运行",而 React Native Web(RNW)把这个承诺延伸到了浏览器:同一份组件代码既可以产出原生 App,也可以编译为标准的 Web 应用。React Native Elements(RNE)则是在这套跨端抽象之上提供的一整套 UI 组件库,组件内部全部基于 React Native 的跨平台 API 实现,因此天然同时适用于 Native 与 Web。

从当前仓库的源码可以印证这一点:RNE 的组件几乎都直接建立在 React Native 的跨平台基元之上。例如 Button.tsx 内部使用的是Pressable,并根据Platform.OS区分 Android 的水波纹(android_ripple)与其他平台的阴影效果;Input.tsx 则是 React NativeTextInput的封装。这种"底层永远只依赖 React Native API"的设计,正是 RNE 能在 RNW 场景下无缝工作的根本原因。

本案例要构建的gitphone是一个 GitHub 仓库查看器:第一屏输入ownerrepo,第二屏展示该仓库的提交记录(作者头像、姓名、提交信息)。它覆盖了跨端应用最常见的三大需求:表单交互、页面路由、远程数据请求。

说明:本文原文发布时 RNE 尚处于1.0.0-beta7阶段;当前仓库中的 RNE 已演进为@rneui/base@rneui/themed两个包(版本5.0.0,见 packages/base/package.json),组件 API 在演进中有扩展与调整,但本文演示的核心用法(Input / Button / Header / ListItem / Avatar / Text)在最新版中依然成立,文末会给出对应的源码佐证。

第一步:初始化项目并验证三端可运行

安装脚手架

create-react-native-web-app(CRNWA)是一个基于 react-native + react-native-web + webpack 的脚手架,一条命令即可生成同时支持 Android、iOS、Web 的项目骨架。全局安装它:

$ npm i -g create-react-native-web-app

创建 gitphone 项目

$ create-react-native-web-app gitphone

脚手架会依次完成:创建项目文件夹、写入项目文件、安装依赖(内部使用 yarn)。安装完成后,进入项目目录,即可使用以下命令:

命令作用
yarn web启动 Web 开发服务器(默认端口 3001,编译成功后在浏览器中访问http://localhost:3001/
yarn android在已连接的 Android 设备/模拟器上构建并安装 APK(需先安装 adb)
yarn ios在 iOS 模拟器上运行(仅限 macOS,需安装 Xcode)
yarn test运行 Native 与 Web 的测试
yarn build构建 Web 生产版本

依次验证三端

Web:运行yarn web,看到Compiled successfully!后,浏览器访问http://localhost:3001/,即可看到脚手架默认页面。

Android:运行yarn android,构建成功后日志会显示Installed on 1 device并自动启动应用:

Installing APK 'app-debug.apk' on 'Redmi 4X - 7.1.2' for app:debug Installed on 1 device. BUILD SUCCESSFUL Total time: 21.783 secs Starting: Intent { cmp=com.creaternwapp/.MainActivity } ✨ Done in 25.64s.

iOS:运行yarn ios,应用应能在模拟器中正常启动。

如果在yarn androidyarn ios阶段构建失败,先不要继续,直接跳到文末的"排错指南"处理,那里给出了 Android(AIDL 缺失)与 iOS(Xcode 10 编译问题)两类典型故障的解决方案。

第二步:接入 React Native Elements 与矢量图标

安装依赖

在项目根目录安装 RNE(当时为 beta 版本)与 React Native Vector Icons(RNVI):

$ yarn add react-native-elements@beta $ yarn add react-native-vector-icons

RNVI 是 RNE 图标能力的底层依赖,原生端需要手动链接:

$ react-native link react-native-vector-icons

解决 webpack 转译报错:把 RNE / RNVI 加入 Babel 转译范围

RNE 与 RNVI 的源码都使用 ES6 编写。对原生端来说这没有问题,但 CRNWA 默认的 webpack 配置通常只转译src目录,不会转译node_modules中的代码。此时运行yarn web会直接报错:

./node_modules/react-native-elements/src/config/withTheme.js Module parse failed: Unexpected token (12:28) You may need an appropriate loader to handle this file type.

解决办法是修改 webpack 配置,在 Babel loader 的include数组中加入这两个包:

  1. 打开config/webpack.config.dev.js,找到注释为"Process JS with babel"(处理 JS 的 babel 配置)的位置,将react-native-elementsreact-native-vector-icons加入include
  2. config/webpack.config.prod.js做同样的修改。

这一改动只影响 webpack 的模块编译范围,不会改变任何业务代码逻辑。从原理上说,webpack 需要先用 Babel 把 ES6 语法降级为浏览器可执行的 ES5 语法,凡是"源码以 ES6 发布"的 npm 包,在 web 构建时都必须加入这个白名单。

现代参考:当前仓库中 RNE 的 web 示例使用了 example/webpack.config.js 配合 website/plugins/react-native-elements-web.js 插件来完成类似的转译与别名处理,可作为升级到新版 RNE 时的配置参考。

验证:用 RNE 的 Button 替换 TouchableHighlight

为了确认集成成功,先做一个最小改动:打开src/App.js,导入 RNE 的Button

import { Button } from 'react-native-elements';

然后把渲染中的TouchableHighlight替换为 RNE 的<Button>。随后分别运行yarn iosyarn androidyarn web,三端都应该正常显示按钮。

源码佐证:当前版本中 Button.tsx 的Button组件除了titleonPress之外,还支持icon(可同时与iconRight使用)、iconPositionleft/right/top/bottom)、typesolid/clear/outline)、loading(显示加载动画并自动屏蔽点击,见handleOnPress中对loading && !disabled的判断)、raiseddisabledsizeradiuscolor等 props——这也解释了后文"Button with Icon"为何能一行完成图文按钮。

提示:Android 端若报Could not find com.android.tools.build.appt2,需要在 Gradle 的仓库配置中加入google仓库。

第三步:构建 Home 表单屏(Input + Button)

组件骨架

src下新建Components文件夹,添加Home.js:包含两个输入框(ownerrepo)和一个提交按钮。在App.js中导入Home并替换原有渲染内容,三端运行验证。

为 Home 添加样式

直接堆组件看起来并不美观,因此新建Components/Shared.style.js集中管理样式对象,在Home中导入并应用到容器、输入区与按钮上。把样式抽到独立文件的做法,方便后续多个组件复用(后面的CommitList也会用到)。

修复 RNVI 在 Web 端的图标字体问题

给输入框或按钮加上图标后,Web 端可能会出现图标不显示的问题。原因在于:RNVI 在 Web 端依赖字体文件(如FontAwesome.ttf),而 webpack 默认不会处理这些.ttf资源。修复分两步:

  1. config/webpack.config.dev.js(以及config/webpack.config.prod.js)中为.ttf增加url-loader规则,把字体文件作为静态资源交给 webpack 处理;
  2. 打开src/index.js,在文档<head>中注入@font-face样式,并引用对应的字体文件路径,让浏览器能够加载图标字体。

完成这两步后,Web 端就能正确渲染 RNVI 图标了。

现代参考:当前仓库的 Expo 示例通过 example/src/helpers/vector-fonts.web.ts 显式声明 FontAwesome、Ionicons、MaterialIcons 等字体的require路径,再由 AssetsCaching.web.tsx 之类的机制注入,本质思路与上面一致——Web 端必须确保字体资源被正确打包与加载。

第四步:跨端路由(react-router)

gitphone有两个页面:Home(输入表单)与CommitList(提交列表)。用户点击 Submit 后跳转到第二屏。这里选择 react-router 系列,因为它在 Native 与 Web 上都能工作:Web 使用BrowserRouter(基于 history API),Native 使用NativeRouter(基于内存栈)。

$ yarn add react-router-dom react-router-native

由于两者不能混用,需要按平台拆分模块。在src下创建Utils文件夹,添加两个文件,内容几乎一致,仅第二行不同:

  • Routing.web.js:导出BrowserRouterRouteSwitchwithRouter等(来自react-router-dom);
  • Routing.native.js:导出同名 API(来自react-router-native)。

React Native / Web 的打包器会根据扩展名自动选择对应文件(.web.js用于 web,.native.js用于原生)。

在 App 中挂接路由

  1. App.js中导入CommitList组件;
  2. Utils/Routing导入RouteRouterSwitch
  3. render方法中实现路由:
<Switch> <Route exact path="/" component={Home} /> <Route path="/commit" component={CommitList} /> </Switch>

用 withRouter 触发跳转

Home.js中:

  1. 导入withRouter并用 HOC 包裹Home组件:
import { withRouter } from '../Utils/Routing'; // ... export default withRouter(Home);
  1. 为按钮添加onPress
<Button title="Submit" onPress={this.onPressButton} />
  1. 实现事件处理,通过history跳转:
onPressButton = () => this.props.history.push('/commit');

withRouter是 react-router 提供的高阶组件,它会向被包裹组件注入historylocationmatch等路由属性,让非路由组件也能发起导航。此时在 Web 与 Android 上测试,应当可以在两个屏幕间来回切换(Submit 进入,系统返回键退出)。

第五步:用 withHeader HOC 复用顶部导航(RNE Header)

为了让CommitList拥有返回/回首页的能力,并且后续新增屏幕时可以复用,这里封装一个withHeaderHOC:

  1. src下创建HOCs文件夹,添加withHeader.js
  2. 导入 RNE 的Header与 RNVI 的 FontAwesome 图标:
import { Header } from 'react-native-elements'; import Icon from 'react-native-vector-icons/FontAwesome';
  1. withHeader接收一个title参数,返回一个包装组件的高阶函数:
const withHeader = ({ title = '' }) => (WrappedComponent) => { // ... };
  1. 在包装组件内部实现导航事件:
goBack = () => this.props.history.goBack(); goHome = () => this.props.history.replace('/');
  1. Header渲染左侧返回按钮、中间标题、右侧首页按钮,例如:
<Header leftComponent={<Icon name="chevron-left" color="#fff" onPress={this.goBack} />} centerComponent={{ text: title, style: { color: '#fff' } }} rightComponent={<Icon name="home" color="#fff" onPress={this.goHome} />} />
  1. CommitList中导入并使用withHeader({ title: 'Commits' })(CommitList)

源码佐证:当前版本的 Header.tsx 完整支持leftComponent/centerComponent/rightComponent三栏布局,placementleft/center/right)控制标题对齐,backgroundColorbackgroundImagelinearGradientProps控制视觉样式,并且内部使用react-native-safe-area-contextSafeAreaView处理刘海屏/状态栏避让,默认edges = ['left', 'top', 'right']——这些能力让withHeader这类封装在现代版本中更加稳健。

第六步:从 GitHub API 拉取提交数据

GitHub 提供了列出仓库提交记录的公开接口:

GET /repos/:owner/:repo/commits

为了演示 RNE × RNW 这一主题,这里先不引入复杂的状态管理,直接用一个轻量 HOC 库fetch-hoc来处理请求生命周期(loading / data / error),并用redux提供的compose把多个 HOC 组合到同一个组件上:

$ yarn add fetch-hoc redux

CommitList.js中:

  1. 导入composefetch
import { compose } from 'redux'; import { fetch } from 'fetch-hoc';
  1. 组合 HOC:
export default compose( withHeader({ title: 'Commits' }), fetch(() => 'https://api.github.com/repos/facebook/react-native/commits') )(CommitList);

此时运行yarn web,打开 DevTools 的 Network 面板并点击 Submit,可以看到返回了一大批 commit 数据(GitHub API 默认返回 30 条)。

第七步:用 ListItem + Avatar 渲染提交列表

需要展示的字段

每条提交记录我们关注三个字段:

author.avatar_url // 提交者头像 commit: author.name // 提交者姓名 message // 提交信息

修改 CommitList 的渲染逻辑

  1. 新增导入:
import { ActivityIndicator, Dimensions, FlatList, Platform, View } from 'react-native'; import { Avatar, ListItem } from 'react-native-elements';
  1. 主渲染改为按状态分发内容:
<View style={styles.container}> {this.renderContent()} </View>
  1. 创建renderContent:加载中显示ActivityIndicator,否则渲染FlatList
renderContent = () => ( this.props.loading ? <ActivityIndicator color='#87ceeb' /> : <FlatList keyExtractor={this.keyExtractor} data={this.props.data} renderItem={this.renderItem} /> )
  1. 创建renderItem:用 RNE 的ListItem展示姓名与提交信息:
renderItem = ({ item }) => ( <ListItem title={item.commit.author.name} subtitle={item.commit.message} leftElement={this.renderLeftElement(item)} /> )
  1. 创建renderLeftElement:用 RNE 的Avatar展示作者头像:
renderLeftElement = (item) => ( <View> <Avatar source={{ uri: item.author.avatar_url }} size='medium' rounded /> </View> )

源码佐证:以上用法在最新版中全部成立:

  • ListItem.tsx 的ListItemBase在传入onPress/onLongPress时自动使用Pressable作为容器(Component = onPress || onLongPress ? Pressable : View),并支持bottomDividertopDividerpad等 props;
  • 其子组件(ListItem.TitleListItem.SubtitleListItem.ContentListItem.Chevron等)位于 packages/base/src/ListItem,leftElement/rightElement用于插入自定义头部/尾部元素;
  • Avatar.tsx 定义了size预设值small: 34, medium: 50, large: 75, xlarge: 150rounded使其呈圆形,title则用于在无图片时渲染文字占位(下一节的边界处理会用到)。

此时运行应用,提交列表就呈现在眼前了:每行一条 commit,左侧是作者头像,右侧是姓名与提交信息。

第八步:表单提交与跨屏传值(formik + history state)

用 formik 接管表单

手写受控表单既繁琐又易错,这里引入 formik 管理表单状态:

$ yarn add formik
  1. Home.js中导入:
import { Formik } from 'formik';
  1. Formik包裹主View,声明初始值与提交回调:
<Formik initialValues={{ owner: '', repo: '' }} onSubmit={this.onPressButton}> {({ handleChange, handleSubmit, values }) => ( <View style={styles.container}>
  1. 把两个 RNEInput与表单状态绑定:
<Input ... onChangeText={handleChange('owner')} value={values.owner} /> <Input ... onChangeText={handleChange('repo')} value={values.repo} />
  1. 按钮的onPress改为handleSubmit
<Button ... onPress={handleSubmit} />
  1. 别忘了闭合ViewFormik
</View> )} </Formik>

源码佐证:RNE 的 Input.tsx 本身就是对 React NativeTextInput的封装,因此TextInput的全部受控属性(valueonChangeText等)都可以直接透传;此外它还额外支持labelerrorMessageleftIcon/rightIcondisabledshake()动画(基于Animated+Easing.bounce,参考 Material Design 动效时长 375ms)等能力。特别地,Input 在渲染时调用了patchWebProps(见 helpers/index.tsx),用于剥离updateThemereplaceThemeonClear等 Web 端不适用的 props,这正是 RNE 组件对 Web 端做兼容处理的微观体现。

通过 history state 跨屏传值

表单提交后,需要把ownerrepo传给第二屏。做法是在跳转时把值放进路由 state:

this.props.history.push({ pathname: '/commit', state: { owner, repo } });

然后在CommitList中读取:

  1. 导入withRouter并把它加入compose
  2. fetch从路由 state 中取参数构造请求 URL:
compose( withHeader({ title: 'Commits' }), withRouter, fetch(({ location: { state = {} } }) => ( `https://api.github.com/repos/${state.owner}/${state.repo}/commits` )) )(CommitList)

注意:HOC 的组合顺序很重要fetch-hoc需要从withRouter注入的location中读取 state,因此withRouter必须位于fetch之前(即组合时先执行更内层的 HOC)。保持与上面一致的顺序,否则拿不到参数。

此时完整测试:在表单输入任意仓库(例如facebook/react-native),点击 Submit,第二屏应当拉取到该仓库的提交记录。

第九步:打磨边界情况

仓库不存在时的错误提示

如果用户输入了一个不存在的仓库会怎样?原生端会红屏,Web 端会白屏。fetch-hoc在请求失败时会返回error状态,利用它来做兜底:

  1. 修改renderContent,在非加载状态下先判断错误:
this.props.loading ? <ActivityIndicator color='#87ceeb' /> : this.renderFlatList()
  1. 从 RNE 导入Text
import { ..., Text } from 'react-native-elements';
  1. 新增renderFlatList,有错误时展示错误信息:
this.props.error ? <Text h4>Error: {this.props.data.message || '😕'}</Text> : <FlatList ... />

再次测试,此时屏幕会显示Error: Not Found而不是红屏/白屏。

源码佐证Text组件(Text.tsx)支持h1/h2/h3/h4布尔 props,配合h4Style等样式 props 即可快速产出标题级文本,这正是上面<Text h4>的用法来源。

作者信息缺失时的占位处理

接着尝试拉取facebook/react-native的提交,又会遇到一个新错误:

Cannot read property 'avatar_url' of null

并非所有提交者的author字段都带有avatar_url(甚至可能为 null)。首先给 Avatar 的source做空值兜底:

source={{ uri: (item.author && item.author.avatar_url) || undefined }}

但直接渲染空头像并不美观,更好的方案是:用正则提取提交者姓名的首字母,通过 Avatar 的titleprops 渲染文字占位:

renderLeftElement = (item) => { const initials = item.commit.author.name.match(/\b\w/g) || []; return ( <View style={leftElementStyle}> <Avatar title={((initials.shift() || '') + (initials.pop() || ''))} ...

/\b\w/g匹配每个单词的首字符,shift()取第一个、pop()取最后一个,拼接后即为姓名缩写(例如 "Havit Rovik" → "HR")。当有真实头像时仍然优先显示头像——Avatar 的title本身就是占位内容,配合source时会在图片加载前显示。

至此,gitphone已经是一个健壮可用的跨端应用:有表单、有路由、有网络请求、有加载态、有错误态、有数据空值兜底。

结论与可扩展方向

回顾整个案例,我们完成了一个同时运行在 iOS、Android 与 Web 三端的 RNE + RNW 应用:✅ 三端运行正常;✅ 使用 react-native-elements 组件;✅ 跨屏路由;✅ GitHub API 请求与渲染。

在此基础上,gitphone还有几个自然的改进方向:

  1. 表单状态保留:从Commits屏返回Home时,输入框内容会清空。引入 redux 将表单值提升到全局状态,即可在返回时保留用户输入;
  2. 无限滚动:GitHub API 默认只返回 30 条。Web 端可以用react-visibility-sensor在滚动接近底部时触发加载下一页;原生端则更简单,直接使用FlatListonEndReached回调即可追加数据。

排错指南

#1yarn android构建失败:aidl is missing

典型报错:

:app:compileDebugAidl FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:compileDebugAidl'. > java.lang.IllegalStateException: aidl is missing BUILD FAILED

修复步骤:

  1. 打开 Android Studio;
  2. 打开gitphone项目下的android目录;
  3. 按提示点击 Update(更新 SDK 组件),等待 Gradle 同步完成;
  4. 同步完成后若提示版本问题,点击Update Build Tools version and sync project
  5. 若仍有Configuration 'compile'警告:打开app/build.gradle,把dependencies中的compile改为implementation
dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:23.0.1" implementation "com.facebook.react:react-native:+" }
  1. 再次同步并关闭 Android Studio,此时yarn android应能成功。

#2yarn ios构建失败(Xcode 10 时代的 double-conversion / libRCTWebSocket 问题)

典型报错(以 double-conversion 的strtod.cc编译失败为例):

** BUILD FAILED ** The following build commands failed: CompileC .../double-conversion-1.1.5/src/strtod.cc ...

这类问题通常是旧版 React Native 与新版 Xcode 的兼容性问题,需要给node_modules/react-native/third-party中的依赖打补丁。若随后又遇到libRCTWebSocket.a链接失败:

The following build commands failed: Libtool .../Debug-iphonesimulator/libRCTWebSocket.a normal x86_64 (1 failure)

可以尝试将已经编译好的libfishhook.a复制到 RN 的 WebSocket 目录,使链接阶段能解析该符号。

说明:以上 iOS 故障发生在 2018 年的旧版 React Native 上,属于当时特定工具链版本的历史问题;如今 Xcode 与 React Native 均已大幅演进,遇到此类问题应优先检查本地 Xcode 版本与 React Native 版本是否匹配。这里保留原始排错思路,供排查旧项目时参考。

附录:仓库内可继续深挖的源码入口

  • packages/base/src/Button/Button.tsx:Button 完整 props 与实现(icon / loading / type / raised / size / radius / color);
  • packages/base/src/Input/Input.tsx:Input 封装与shake动画、patchWebProps调用;
  • packages/base/src/Header/Header.tsx:三栏 Header 与 SafeArea 处理;
  • packages/base/src/ListItem/ListItem.tsx:ListItem 容器与子组件体系;
  • packages/base/src/Avatar/Avatar.tsx:Avatar 的 size 预设、rounded、title 占位;
  • packages/base/src/Text/Text.tsx:Text 的 h1–h4 标题能力;
  • packages/base/src/helpers/index.tsx:renderNodepatchWebProps等跨端兼容工具;
  • example/src/helpers/vector-fonts.web.ts:Web 端图标字体声明的现代参考实现;
  • example/webpack.config.js:Web 构建配置参考;
  • packages/base/src/index.ts:组件统一导出入口。

【免费下载链接】react-native-elementsCross-Platform React Native UI Toolkit项目地址: https://gitcode.com/gh_mirrors/re/react-native-elements

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 2:33:01

自然语言驱动开发完全指南:Vibe Coding工具选型与实操避坑

在开始之前&#xff0c;先把我最近的体验放前面&#xff1a;我花了两周时间&#xff0c;用自然语言重构了一个内部数据清洗脚本&#xff0c;从需求描述到最终跑通&#xff0c;几乎没亲手写过完整函数。这个过程的爽感是真实的&#xff0c;但踩坑的数量也是真实的。工具没选对&a…

作者头像 李华
网站建设 2026/9/20 2:32:41

Simulink与ISO 26262:功能安全开发中的工具链落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 2:32:35

SYCL 矩阵乘法 CPU/GPU 结果偏差?让 Codex 走 TaoToken 照 verify 查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 2:32:15

MODIS MOD44B植被覆盖数据处理全流程:2000-2020中国区实践

MODIS 这摊子事&#xff0c;玩遥感的几乎没有不知道MOD44B的。这个产品全名叫 Vegetation Continuous Fields&#xff0c;中文圈一般叫“植被连续场”或者干脆叫“植被覆盖百分比”。我这次做的是把 2000 到 2020 年中国区域的 MOD44B 数据整理成一套干净可用的植被覆盖百分比数…

作者头像 李华