大家好,这里是韶光日记开发团队,虽然叫团队,但是目前只有我一个人~

 

为什么决定做独立应用

决定做独立应用的开发是在23年春节后,当时一个人在出租屋,刷着手机,无意间小宇宙开始爆发,决定不再颓废下去,正好当时心情烦闷,于是决定搞个日记app来玩玩(经典开场!!)

技术选型

技术选型可选的并不是很多,首先原生应用开发并不熟悉,对于一个前端开发来说最合适的无非就是跨端开发:

  • uniapp
  • flutter
  • react-native

uniapp 之前做过一个电商的小程序,并且适配了多端(小程序,h5,app),综合下来开发一些页面还可以,但是没办法谈性能和体验。

flutter 当时只是知道这个东西,大概看了一眼,学习成本也比较高,所以直接舍弃了

所以作为一个 react 死忠粉,我又盯上了 react-native,说起来大概在18年的时候,用react-native 做过一款外卖app,开发体验还算不错,但是由于当时对rn的性能优化以及对应用开发模式的不熟悉,导致做出来一坨翔。现在随着经验越来越丰富,决定还是把rn捞出来再试试。

准备工作

当时大概统计了一下,总共要解决以下问题,就可以做出来一个Demo

  • 富文本编辑器
  • 富文本渲染器
  • 组件库
  • 数据库
  • 路由导航
  • 本地缓存
  • 图片存储
  • 日历控件
  • 长列表滚动

富文本编辑器 

最初富文本编辑器是直接在 npm 上面找了个现成的,但是集成以后发现功能实在简单,并且想支持行内的图片显示必须得把图片放在服务器上,然后塞url进去,所以最初版本的图片是当做附件传入的,虽然勉强,但也不是不能用,先有了再改,主打一个程序员的自我修养。
 

 

后来由于实在功能不够用,基于 lexical 实现了一个webview的富文本编辑器

富文本渲染器

富文本编辑器的渲染采用了 react-native-render-html ,不得不说开发者是真的强,我可以直接把富文本丢进去,然后写一套公共的样式即可,总的来说跟平时在web上开发没什么太大的区别。

 

当然,还是有很多花活可以去整,比如说自定义一个标签:

<RenderHtml
  enableExperimentalBRCollapsing
  contentWidth={width || ScreenWidth}
  source={{
    html: newValue,
  }}
  renderers={{
    img: (prop) => {
      const imageData = getRenderImage(prop.tnode.init.domNode.attribs.uuid)
      return (
        <View style={{}}>
          <FastImage
            resizeMode='contain'
            style={{
              width: '100%',
              // height: imageData.height,
              borderRadius: 12,
              aspectRatio: imageData.width / imageData.height,
            }}
            source={{
              uri: imageData.uri,
            }}
          />
        </View>
      )
    },
  }}
  enableExperimentalMarginCollapsing={true}
  backgroundColor={Colors.transparent}
/>

组件库

其实当时对我来说,做一款app简单并不清楚上线等问题,完全就是一股脑的冲动,甚至连ui都没有,但是对于一个经验丰富的程序员来说,有个组件库,可以解决一切问题。

组件库考虑了很多,比如 react-native-elements react-native-paper 等等,最终选择了  react-native-ui-lib,原因就是它不仅好看,还有成型的demo

 

并且组件的数量可比较可观,还有丰富的颜色管理系统和静态资源管理系统,更是大大缩短了项目启动的时间。

当然最喜欢的还是下面这种写法~

function App() {
  return (
    <View paddingH-30>
      <Text color={Colors.textTitle} text60 marginB-10>
        {t('故事')}
      </Text>
      <Text color={Colors.textTitle} text80 marginB-20>
        {t('在纷繁的世界中,我们渴望将生活中的珍贵瞬间封存在心底,让它们永不褪色。')}
      </Text>
    </View>
  )
}

数据库

当时不想搞服务器端,所以摆在我面前的选择就剩本地数据库可选了:

  • Realm
  • react-native-sqlite-storage

由于自己对数据库的不熟悉,以及懒得写sql,所以本人义无反顾的选择了 Realm ,这也是后面噩梦的开始,当然这个都是后话了。

Realm 当然是有自己的独到之处的,首先对rn有很好的支持,并且可以通过hook跟react有很好的集成,但是绑定的太深我不是很喜欢,所以我还是采用了比较保守的方式做了集成,通过实现了一套获取数据的api来实现数据的增删改查。

import Realm from 'realm';
import {
  Diary,
  deleteRealm,
  writeToRealm,
  queryRowFromRealm,
  queryAllFromRealm,
  clearAllFromRealm,
  clearRowFromRealm,
} from './index';

export const name = 'Colors';

export const schema = {
  name,
  primaryKey: 'id',
  properties: {
    id: 'string',
    label: 'string',
    color: 'string',
    delete: {type: 'bool', default: false},
    createTime: {type: 'date', default: new Date()},
  },
};

// 写入数据
export function write(obj) {
  return writeToRealm(obj, name);
}

export function deleteDiary(obj) {
  return deleteRealm(obj, name);
}

// 查询指定数据
export function queryRow(id) {
  return queryRowFromRealm(id, name);
}

// 查询数据
export function queryAll(query = '', start, end) {
  return queryAllFromRealm(name, `${query}delete == false`, start, end);
}

// 查询日记数据
export function queryHandAll(query = '', start, end) {
  return queryAllFromRealm(name, `${query}delete == false`, start, end);
}

// 清除数据
export function clearAll() {
  return clearAllFromRealm(name);
}

// 删除行
export function clearRow(id) {
  return clearRowFromRealm(id, name);
}

路由导航

路由前期选择了@react-navigation/native-stack 但是在安卓的动画实在没找到怎么改,所以后续老老实实改回了 @react-navigation/stack算是踩了最大的一个坑。

 

不过本人实在喜欢剑走偏锋, 所以只是用了本身的路由功能,导航头什么的都自己写了,省的研究api,以至于走火入魔。

<Stack.Screen
  name="Info"
  options={{title: t('故事详情'), headerShown: false}}
  component={Info}
/>
<Layout ref={layoutRef}>
  <ActionBar rights={rights} actions={action} />
  <FlashList
    nestedScrollEnabled
    ref={scrollRef}
    enableDynamicSizing
    showsHorizontalScrollIndicator={false}
    showsVerticalScrollIndicator={false}
    ListHeaderComponent={header}
    estimatedItemSize={300}
    data={data.data}
    renderItem={renderContent}
    keyExtractor={item => item?.id || Date.now()}
    ListEmptyComponent={empty}
    ListFooterComponent={footer}
    onScroll={event => {
      offsetY.value = event.nativeEvent.contentOffset.y;
      setShowTitle(event.nativeEvent.contentOffset.y > 50);
    }}
    extraData={[batchEdit, batchEditList, data]}
    onEndReachedThreshold={0.5}
    onEndReached={onEndReached}
  />
</Layout>
 

本地缓存

本地存储不同于数据库,准确来讲相当于app端的 localStorage 用来存储一些状态,这个没什么好讲的,无脑上了 @react-native-async-storage/async-storage 。

 

 

当然代价还是有的,前期获取配置信息都是通过 redux 同步去取数据,导致启动时间过长,后来发现 AsyncStorage.multiGet 可以一次性获取到想要的配置,大大缩短了启动时间。

另外在后续实现日记的草稿功能时,无意间在安卓手机上发现塞的内容太多,会导致内存爆掉,所以还是要控制一下写入的数据大小,尽量少存一些东西。

图片存储

本地存储图片依赖了 react-native-fs @react-native-camera-roll/camera-roll 一个用来存储照片,一个用来从相册或者相机获取照片,并且提供了压缩或者转base64的功能。

在新的富文本编辑器中,还通过读取图片的base64编码注入到编辑器,实现了本地图片插入富文本的功能。

日历控件

日历控件找了很多,最终用了 react-native-calendars ,只不过已经停更很久了,并且存在一些bug,拉下来源码魔改了一下,实现了现在的功能,

长列表滚动

长列表的滚动在任何情况下都有的性能问题,尤其是在移动端,对于交互和性能有很高的要求,rn本身提供了一些高性能列表的优化方案,但是奈何本人实在是想直接把富文本摊到列表页,导致性能问题更加严重,经过多方尝试,最终采用了  flash-list 才解决掉这个问题。

 

 

总结

这个就是我的第一款独立应用,主要是做了一些技术上的大胆尝试,目前运营情况说不上好,也有一些用户在长期使用,希望能越做越好。

韶光日记开发团队,祝好!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0
0