第3章 3.4 uni-ui 官方组件库

⏱️ 阅读时间约 90 分钟 | 难度:进阶 ⭐⭐☆ | 配套视频:暂无


🎯 开场 3 分钟:为什么要学这个?

上一章我们学了 uView UI 这个第三方组件库,用起来确实香。但你有没有想过:万一哪天 uView 不维护了怎么办? 或者你进了个公司,项目里只让用官方的东西——这时候你就得认识认识 uni-app 的「亲儿子」了。

uni-ui 是 DCloud 官方出的组件库,跟 uni-app 是一家人,适配性更好,更新更及时。

举个例子:就像手机出厂自带的原生应用,虽然不一定最好用,但肯定最稳定、最不会被卸载。

本文学完,你能掌握:
- uni-ui 怎么装、怎么用
- 三个高频组件:uni-icons 图标、uni-data-select 数据选择器、uni-forms 表单
- 能独立把这些组件用到实战项目里


🧱 基础 25 分钟:核心概念

3.4.1 uni-ui 是什么?

uni-ui 是 DCloud 官方维\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n护的 UI 组件库,专门为 uni-app 生态打造。

生活类比:把 uni-app 想象成手机系统,uni-ui 就是系统出厂自带的「原生应用」,而 uView 就像从应用商店下载的「第三方应用」。原生应用不一定功能最全,但一定最听话(兼容性最好)、最准时更新(官方维护)。

为什么要用 uni-ui?
1. 官方亲儿子 —— 跟 uni-app 内核同步更新,出问题找得到人
2. easycom 兼容 —— 不用手动 import,直接用标签就能跑
3. 全端支持 —— H5、微信小程序、安卓、iOS 一套代码走天下

3.4.2 安装 uni-ui

打开终端,在项目根目录执行:

npm install @dcloudio/uni-ui

安装完成后,在 pages.json 中确认是否有 easycom 配置(一般 uni-app 项目默认有):

{
"easycom": {
"autoscan": true,
"custom": {
  "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
}
}

如果没有,手动加进去。配置好之后,不需要再做任何 import,直接用 <uni-xxx> 标签就行。

3.4.3 uni-icons 图标组件

是什么:uni-ui 自带的图标库,内置了大量常用图标。

怎么用:直接上标签,图标名称从官方文档查。

<template>
<view class="container">
<text class="title">uni-icons 图标演示</text>

<!-- 基础用法:直接用图标名 -->
<uni-icons type="home" size="30" color="#333"></uni-icons>

<!-- 带点击事件 -->
<uni-icons type="search" size="30" @click="handleSearch"></uni-icons>

<!-- 配合文字使用 -->
<view class="icon-row">
  <uni-icons type="plus" size="20"></uni-icons>
  <text>添加</text>
</view>
</view>
</template>

<script>
export default {
data() {
return {}
},
methods: {
handleSearch() {
  console.log('点击了搜索图标')
  uni.showToast({ title: '搜索图标被点击', icon: 'none' })
}
}
}
</script>

<style>
.container {
padding: 20px;
}
.icon-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
</style>

这行 type="home" 是图标名称,size="30" 控制大小,color="#333" 控制颜色。图标名去 uni-icons 官方文档 查表就行。

3.4.4 uni-data-select 数据选择器

是什么:一个带下拉搜索的数据选择器,比原生的 picker 好用太多。

生活类比:就像外卖点餐时点「收藏的商家」——你搜一个店名,下拉列表自动筛选,点一下就选中。

为什么用:原生 picker 不能搜索,数据多了很难找。uni-data-select 支持输入过滤,体验好很多。

<template>
<view class="container">
<text class="label">选择城市:</text>

<!-- 本地数据模式 -->
<uni-data-select
  v-model="selectedCity"
  :localdata="cityList"
  placeholder="请选择城市"
  @change="onCityChange"
></uni-data-select>

<text class="result">你选中的城市:{{ selectedCity || '暂无' }}</text>
</view>
</template>

<script>
export default {
data() {
return {
  selectedCity: '',
  // 城市列表,value 是实际值,text 是显示文字
  cityList: [
    { value: 'beijing', text: '北京' },
    { value: 'shanghai', text: '上海' },
    { value: 'guangzhou', text: '广州' },
    { value: 'shenzhen', text: '深圳' },
    { value: 'hangzhou', text: '杭州' },
    { value: 'chengdu', text: '成都' }
  ]
}
},
methods: {
onCityChange(e) {
  console.log('选中的值:', e)
  const city = this.cityList.find(item => item.value === e)
  uni.showToast({
    title: `你选择了 ${city ? city.text : ''}`,
    icon: 'none'
  })
}
}
}
</script>

<style>
.container {
padding: 20px;
}
.label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
.result {
display: block;
margin-top: 15px;
color: #666;
}
</style>

localdata 传数组,每项有 value(实际值)和 text(显示文字)。v-model 绑定选中的值,变化时触发 @change

3.4.5 uni-forms 表单组件

是什么:一套表单解决方案,支持表单验证、自动收集数据、报错提示。

生活类比:就像租房时的「模板合同」——房型、租金、押金都有固定格子,你只要往里填就行,漏填了还自动标红提醒。

为什么用:自己做表单验证要写一堆 if-else,用 uni-forms 只需要配置规则就行。

<template>
<view class="container">
<uni-forms ref="myForm" :modelValue="formData" :rules="rules">
  <!-- 用户名输入 -->
  <uni-forms-item label="用户名" name="username">
    <uni-easyinput
      v-model="formData.username"
      placeholder="请输入用户名"
      clearable
    />
  </uni-forms-item>

  <!-- 邮箱输入 -->
  <uni-forms-item label="邮箱" name="email">
    <uni-easyinput
      v-model="formData.email"
      placeholder="请输入邮箱"
      type="text"
    />
  </uni-forms-item>

  <!-- 密码输入 -->
  <uni-forms-item label="密码" name="password">
    <uni-easyinput
      v-model="formData.password"
      placeholder="请输入密码"
      type="password"
      clearable
    />
  </uni-forms-item>

  <!-- 提交按钮 -->
  <button @click="submitForm" class="submit-btn">提交</button>
</uni-forms>
</view>
</template>

<script>
export default {
data() {
return {
  formData: {
    username: '',
    email: '',
    password: ''
  },
  // 表单验证规则
  rules: {
    username: {
      rules: [
        { required: true, errorMessage: '用户名不能为空' },
        { minLength: 3, errorMessage: '用户名至少3个字符' }
      ]
    },
    email: {
      rules: [
        { required: true, errorMessage: '邮箱不能为空' },
        { format: 'email', errorMessage: '邮箱格式不正确' }
      ]
    },
    password: {
      rules: [
        { required: true, errorMessage: '密码不能为空' },
        { minLength: 6, errorMessage: '密码至少6位' }
      ]
    }
  }
}
},
methods: {
submitForm() {
  this.$refs.myForm.validate().then(res => {
    console.log('表单数据:', res)
    uni.showToast({
      title: '提交成功!',
      icon: 'success'
    })
  }).catch(err => {
    console.log('校验失败:', err)
  })
}
}
}
</script>

<style>
.container {
padding: 20px;
}
.submit-btn {
margin-top: 20px;
background-color: #007aff;
color: #fff;
}
</style>

uni-forms 包裹整个表单,uni-forms-item 包裹每个字段,uni-easyinput 是自带的输入框组件。规则写在 rules 里,required: true 表示必填,format: 'email' 表示要校验邮箱格式。


🔥 实战 35 分钟:3 个递进的小项目

📦 项目 1:图标工具栏(5 分钟)

目标:用 uni-icons 做一个底部工具栏,点击有反馈。

<template>
<view class="page">
<text class="title">我的工具栏</text>

<!-- 中间内容区 -->
<view class="content">
  <text>当前选中:{{ currentTab }}</text>
</view>

<!-- 底部图标导航 -->
<view class="tab-bar">
  <view
    class="tab-item"
    :class="{ active: currentTab === 'home' }"
    @click="switchTab('home')"
  >
    <uni-icons :type="currentTab === 'home' ? 'home' : 'home'" size="24"></uni-icons>
    <text class="tab-text">首页</text>
  </view>

  <view
    class="tab-item"
    :class="{ active: currentTab === 'list' }"
    @click="switchTab('list')"
  >
    <uni-icons type="list" size="24"></uni-icons>
    <text class="tab-text">列表</text>
  </view>

  <view
    class="tab-item"
    :class="{ active: currentTab === 'cart' }"
    @click="switchTab('cart')"
  >
    <uni-icons type="cart" size="24"></uni-icons>
    <text class="tab-text">购物车</text>
  </view>

  <view
    class="tab-item"
    :class="{ active: currentTab === 'user' }"
    @click="switchTab('user')"
  >
    <uni-icons type="user" size="24"></uni-icons>
    <text class="tab-text">我的</text>
  </view>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  currentTab: 'home'
}
},
methods: {
switchTab(tab) {
  this.currentTab = tab
  uni.showToast({
    title: `切换到${tab}`,
    icon: 'none'
  })
}
}
}
</script>

<style>
.page {
height: 100vh;
display: flex;
flex-direction: column;
}
.content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.tab-bar {
display: flex;
justify-content: space-around;
padding: 10px 0;
border-top: 1px solid #eee;
background: #fff;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 5px 15px;
}
.tab-item.active {
color: #007aff;
}
.tab-text {
font-size: 12px;
margin-top: 4px;
}
</style>

预期输出:点击不同图标,顶部文字更新,底部高亮切换,弹出 toast 提示。

一句话解释:用 currentTab 状态控制哪个图标高亮,@click 切换状态。


📦 项目 2:城市选择器 + 缓存(15 分钟)

目标:用 uni-data-select 做城市选择,选中后存到本地,下次打开自动恢复。

<template>
<view class="container">
<text class="title">城市选择器</text>

<uni-data-select
  v-model="selectedCity"
  :localdata="cityList"
  placeholder="请搜索或选择城市"
  @change="onCityChange"
></uni-data-select>

<view class="info-card">
  <text class="info-title">当前城市信息</text>
  <text>城市代码:{{ selectedCity || '未选择' }}</text>
  <text>城市名称:{{ cityName }}</text>
  <text>选择次数:{{ selectCount }} 次</text>
</view>

<button @click="clearStorage" class="clear-btn">清除缓存</button>
</view>
</template>

<script>
export default {
data() {
return {
  selectedCity: '',
  selectCount: 0,
  cityList: [
    { value: '110000', text: '北京' },
    { value: '120000', text: '天津' },
    { value: '130000', text: '石家庄' },
    { value: '140000', text: '太原' },
    { value: '150000', text: '呼和浩特' },
    { value: '210000', text: '沈阳' },
    { value: '220000', text: '长春' },
    { value: '230000', text: '哈尔滨' },
    { value: '310000', text: '上海' },
    { value: '320000', text: '南京' },
    { value: '330000', text: '杭州' },
    { value: '340000', text: '合肥' },
    { value: '350000', text: '福州' },
    { value: '360000', text: '南昌' },
    { value: '370000', text: '济南' },
    { value: '410000', text: '郑州' },
    { value: '420000', text: '武汉' },
    { value: '430000', text: '长沙' },
    { value: '440000', text: '广州' },
    { value: '450000', text: '南宁' },
    { value: '460000', text: '海口' },
    { value: '500000', text: '重庆' },
    { value: '510000', text: '成都' },
    { value: '520000', text: '贵阳' },
    { value: '530000', text: '昆明' },
    { value: '610000', text: '西安' },
    { value: '620000', text: '兰州' },
    { value: '630000', text: '西宁' },
    { value: '640000', text: '银川' },
    { value: '650000', text: '乌鲁木齐' }
  ]
}
},
computed: {
cityName() {
  const city = this.cityList.find(item => item.value === this.selectedCity)
  return city ? city.text : '未选择'
}
},
onLoad() {
// 页面加载时读取缓存
const cachedCity = uni.getStorageSync('lastCity')
const cachedCount = uni.getStorageSync('selectCount')

if (cachedCity) {
  this.selectedCity = cachedCity
}
if (cachedCount) {
  this.selectCount = parseInt(cachedCount)
}
},
methods: {
onCityChange(e) {
  if (!e) return

  this.selectCount += 1

  // 保存到本地缓存
  uni.setStorageSync('lastCity', e)
  uni.setStorageSync('selectCount', this.selectCount.toString())

  const city = this.cityList.find(item => item.value === e)
  uni.showToast({
    title: `已选择 ${city ? city.text : ''}`,
    icon: 'none'
  })
},
clearStorage() {
  uni.removeStorageSync('lastCity')
  uni.removeStorageSync('selectCount')
  this.selectedCity = ''
  this.selectCount = 0
  uni.showToast({
    title: '缓存已清除',
    icon: 'success'
  })
}
}
}
</script>

<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
display: block;
}
.info-card {
margin-top: 30px;
padding: 15px;
background: #f5f5f5;
border-radius: 8px;
}
.info-title {
display: block;
font-weight: bold;
margin-bottom: 10px;
}
.info-card text {
display: block;
margin-bottom: 5px;
}
.clear-btn {
margin-top: 20px;
}
</style>

预期输出:选择城市后刷新页面,城市依然保持;清除缓存后重置。

一句话解释:用 uni.getStorageSyncuni.setStorageSync 做本地持久化,onLoad 时读取恢复。


📦 项目 3:注册表单 + 数据提交(15 分钟)

目标:用 uni-forms 做完整注册表单,包含验证、数据组装、模拟提交。

<template>
<view class="container">
<text class="title">用户注册</text>

<uni-forms ref="registerForm" :modelValue="formData" :rules="rules">
  <!-- 用户名 -->
  <uni-forms-item label="用户名" name="username">
    <uni-easyinput
      v-model="formData.username"
      placeholder="3-10位字母或数字"
      clearable
    />
  </uni-forms-item>

  <!-- 邮箱 -->
  <uni-forms-item label="邮箱" name="email">
    <uni-easyinput
      v-model="formData.email"
      placeholder="请输入邮箱"
      type="text"
    />
  </uni-forms-item>

  <!-- 手机号 -->
  <uni-forms-item label="手机号" name="phone">
    <uni-easyinput
      v-model="formData.phone"
      placeholder="请输入手机号"
      type="number"
      maxlength="11"
    />
  </uni-forms-item>

  <!-- 所在城市 -->
  <uni-forms-item label="城市" name="city">
    <uni-data-select
      v-model="formData.city"
      :localdata="cityOptions"
      placeholder="请选择城市"
    ></uni-data-select>
  </uni-forms-item>

  <!-- 密码 -->
  <uni-forms-item label="密码" name="password">
    <uni-easyinput
      v-model="formData.password"
      placeholder="6位以上密码"
      type="password"
      clearable
    />
  </uni-forms-item>

  <!-- 确认密码 -->
  <uni-forms-item label="确认密码" name="confirmPassword">
    <uni-easyinput
      v-model="formData.confirmPassword"
      placeholder="再次输入密码"
      type="password"
      clearable
    />
  </uni-forms-item>

  <button @click="handleRegister" class="submit-btn">注册</button>
</uni-forms>
</view>
</template>

<script>
export default {
data() {
// 自定义校验:确认密码
const validatePass = (rule, value, data, callback) => {
  if (value !== this.formData.password) {
    callback(new Error('两次输入的密码不一致'))
  } else {
    callback()
  }
}

return {
  formData: {
    username: '',
    email: '',
    phone: '',
    city: '',
    password: '',
    confirmPassword: ''
  },
  rules: {
    username: {
      rules: [
        { required: true, errorMessage: '用户名不能为空' },
        { minLength: 3, errorMessage: '用户名至少3个字符' },
        { maxLength: 10, errorMessage: '用户名最多10个字符' },
        { pattern: /^[a-zA-Z0-9]+$/, errorMessage: '只能输入字母或数字' }
      ]
    },
    email: {
      rules: [
        { required: true, errorMessage: '邮箱不能为空' },
        { format: 'email', errorMessage: '邮箱格式不正确' }
      ]
    },
    phone: {
      rules: [
        { required: true, errorMessage: '手机号不能为空' },
        { pattern: /^1[3-9]\d{9}$/, errorMessage: '手机号格式不正确' }
      ]
    },
    city: {
      rules: [
        { required: true, errorMessage: '请选择城市' }
      ]
    },
    password: {
      rules: [
        { required: true, errorMessage: '密码不能为空' },
        { minLength: 6, errorMessage: '密码至少6位' }
      ]
    },
    confirmPassword: {
      rules: [
        { required: true, errorMessage: '请确认密码' },
        { validateFunction: validatePass }
      ]
    }
  },
  cityOptions: [
    { value: 'beijing', text: '北京' },
    { value: 'shanghai', text: '上海' },
    { value: 'guangzhou', text: '广州' },
    { value: 'shenzhen', text: '深圳' },
    { value: 'hangzhou', text: '杭州' },
    { value: 'chengdu', text: '成都' },
    { value: 'wuhan', text: '武汉' }
  ]
}
},
methods: {
handleRegister() {
  this.$refs.registerForm.validate().then(res => {
    console.log('表单数据:', JSON.stringify(this.formData, null, 2))

    // 模拟提交到后端
    uni.showLoading({ title: '注册中...' })

    setTimeout(() => {
      uni.hideLoading()
      uni.showModal({
        title: '注册成功',
        content: `欢迎 ${this.formData.username}!你的城市是 ${this.getCityName(this.formData.city)}`,
        showCancel: false
      })
    }, 1000)
  }).catch(err => {
    console.log('校验失败:', err)
    uni.showToast({
      title: '请检查表单填写',
      icon: 'none'
    })
  })
},
getCityName(cityValue) {
  const city = this.cityOptions.find(item => item.value === cityValue)
  return city ? city.text : cityValue
}
}
}
</script>

<style>
.container {
padding: 20px;
}
.title {
font-size: 20px;
font-weight: bold;
margin-bottom: 25px;
display: block;
text-align: center;
}
.submit-btn {
margin-top: 25px;
background-color: #007aff;
color: #fff;
}
</style>

预期输出:填完整表单点注册,通过验证后弹出成功对话框;填写错误会提示具体哪里错。

一句话解释uni-formsvalidate() 返回 Promise,then 里是校验通过的表单数据,catch 里是错误信息。


💪 进阶 20 分钟:常见坑 + 性能小贴士

⚠️ 坑 1:uni-data-select 的值是 string 不是 number

<!-- ❌ 错误示例:后端返回的是数字,但 select 要求字符串 -->
<uni-data-select v-model="formData.id" ...></uni-data-select>

<!-- 假设后端返回的 id 是 123 -->
data: { id: 123 }  // 会导致选不上
<!-- ✅ 正确示例:统一转成字符串 -->
data: { id: '123' }

// 或者在数据返回时做转换
onLoad() {
const res = await getUserInfo()
res.data.id = String(res.data.id)
this.formData = res.data
}

原因:uni-data-select 内部用 === 比较,类型不一致就匹配不上。


⚠️ 坑 2:uni-forms 在 onLoad 时获取不到值

<!-- ❌ 错误示例:在 onLoad 里直接读 formData -->
onLoad() {
console.log(this.formData.username) // undefined,因为还没初始化完
}
<!-- ✅ 正确示例:用 nextTick 或 $nextTick -->
onLoad() {
this.$nextTick(() => {
console.log(this.formData.username)
})
}

// 或者在 mounted 生命周期
mounted() {
console.log(this.formData.username)
}

⚠️ 坑 3:uni-icons type 写错了不报错,静默失败

<!-- ❌ 错误示例:拼写错误,图标不显示但控制台没报错 -->
<uni-icons type="hom" size="30"></uni-icons>  <!-- hom 不是有效图标名 -->

<!-- ✅ 正确示例:查文档确认图标名 -->
<uni-icons type="home" size="30"></uni-icons>

技巧:写图标名时打开 uni-icons 文档 对着抄。


⚠️ 坑 4:uni-forms 的 rules 写在 data 里拿不到 this

<!-- ❌ 错误示例:rules 里想用 this.cityList,但 this 是 undefined -->
data() {
return {
cityList: [...],
rules: {
  city: {
    rules: [
      { validateFunction: (rule, value, callback) => {
        // 这里 this 是 undefined!
        if (this.cityList.find(...)) callback()
      }}
    ]
  }
}
}
}
<!-- ✅ 正确示例:把 validateFunction 写到 methods 里 -->
methods: {
validateCity(rule, value, callback) {
if (this.cityList.find(item => item.value === value)) {
  callback()
} else {
  callback(new Error('城市不存在'))
}
}
}

// 在 data 的 rules 里引用
rules: {
city: {
rules: [{ validateFunction: this.validateCity }]
}
}

⚠️ 坑 5:v-model 绑定的值类型不对导致验证失效

<!-- ❌ 错误示例:初始值类型跟实际值类型不一致 -->
data() {
return {
formData: {
  age: 0  // 数字类型
}
}
}

// 用户输入 "25",但表单验证时类型不一致
<!-- ✅ 正确示例:初始值类型保持一致 -->
data() {
return {
formData: {
  age: ''  // 空字符串,类型一致
}
}
}

🛠️ 调试技巧:console.log 打印表单数据

// 提交时打印完整表单
handleSubmit() {
this.$refs.myForm.validate().then(res => {
// 格式化打印,方便查看
console.log('提交数据:', JSON.stringify(this.formData, null, 2))
})
}

✏️ 练习题 + 作业题

练习题(5 道,10 分钟内完成)

练习 1(2 分钟):换一个图标类型
- 输入:把项目 1 中的 type="home" 改成 type="image",运行看效果
- 预期输出:图标从「家」变成「图片」图标
- 提示:去文档查「image」是不是有效图标名

练习 2(2 分钟):限制只能选特定城市
- 输入:在项目 2 的城市列表中,只保留「北京、上海、广州、深圳」
- 预期输出:下拉列表只有 4 个城市可选
- 提示:直接删掉 cityList 里不需要的项就行

练习 3(2 分钟):加一个必填校验
- 输入:给项目 3 的表单加一个「昵称」字段,要求必填
- 预期输出:不填昵称点提交,提示「昵称不能为空」
- 提示:在 formData 加 nickname,在 rules 加 nickname 的规则

练习 4(2 分钟):城市选择后显示 emoji
- 输入:在项目 2 中,选中城市后在 cityName 前面加个 🏠 emoji
- 预期输出:显示「🏠北京」而不是「北京」
- 提示:在 computed 的 cityName 前面拼接 emoji 字符串

练习 5(2 分钟):找到报错原因
- 输入:以下代码运行时报错,分析原因

<uni-data-select v-model="formData.id" :localdata="list"></uni-data-select>

<script>
export default {
data() {
return {
  formData: { id: 123 },  // 后端返回的数字
  list: [
    { value: '123', text: '选项1' },  // 字符串
    { value: '456', text: '选项2' }
  ]
}
}
}
</script>
  • 预期输出:选择任意选项,v-model 的值不变
  • 提示:类型不一致,123 !== '123'

📝 作业题(30 分钟 - 2 小时)

作业:做一个「个人资料设置页」

  • 需求描述:做一个完整的个人资料编辑页面,包含头像(用图标模拟)、昵称、性别、所在城市、生日、个性签名

  • 功能点
    1. 用 uni-icons 模拟头像位置(一个大的 person 图标)
    2. 用 uni-forms + uni-easyinput 做表单,支持验证
    3. 性别用 uni-data-select 选择(男/女/保密)
    4. 城市用 uni-data-select 选择
    5. 个性签名用多行文本输入
    6. 点保存时验证全部字段,通过后显示「保存成功」

  • 加分项
    1. 页面加载时从本地缓存读取已有资料
    2. 生日字段用 uni-dateformat 或自定义格式显示

  • 验收标准

  • 能跑起来
  • 所有字段都有验证
  • 不填必填项点保存会报错
  • 填写正确点保存会弹成功提示

  • 提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

本章收获

本文我们学了 3 个核心知识点:

  1. uni-ui 是什么:DCloud 官方的组件库,兼容性好,easycom 免导入
  2. 三个高频组件:uni-icons(图标)、uni-data-select(下拉选择)、uni-forms(表单验证)
  3. 数据持久化:用 uni.getStorageSync 做本地缓存,刷新不丢失

延伸资源


🎤 互动钩子

你在项目里更倾向用 uni-ui 还是 uView?为什么? 评论区聊聊,优缺点都说说,老粉优先回复!


📢 下章预告

学会了组件库,是时候做个完整的页面了。下一章我们要用学到的组件知识,仿做一个掘金首页 + 文章列表,从 0 到 1 搭一个完整的资讯类 App 页面。敬请期待!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。