用 Tauri + React 仿写一个酷安社区PC端

10,768 阅读4分钟

起因

前天日常刷酷安的时候看到了一个酷安UWP版的一条动态,作为一个菜鸡前端第一时间想到的是我能不能这样做一个PC版本的应用,第一时间想到了了的是用electron来做,在一番搜索过后,发现了electron的原理是内置了Chromium内核,所以裸打包的话就有62.5mb的体积,如何我就想到了Tauri这个框架,前端是通过系统的 WebView2,后端使用 Rust,裸打包很小(4.32MB)。但是本人对Rust一窍不通emm,但是认真想了之后我发现并不需要使用到Tauri中Rust对后端的操作。因此最终选择是Tauri(嘿嘿)!!

🌟Github项目仓库:github.com/ayuan-gy/Co… 求🌟

效果图gif(比较大)

20220806_20651 (2).gif

Tauri 是什么?

Tauri 使用 Web 前端构建更小、更快、更安全的桌面应用程序。

酷安是什么

酷安是深圳酷安网络科技有限公司旗下的一个泛科技数码社区,口号是「发现科技新生活」,前身为知名第三方安卓应用市场「酷市场」,拥有海量数码爱好者用户,在国内数码科技圈具备一定影响力。 公司旗下包含手机客户端「酷安」、B 站数码评测账号「酷安数码」、微信公众号「酷安数码」、微信小程序「酷安App」、微博官方账号「酷安网」。

真正的介绍-> 酷基交友社区,Blued的竞争对手人称小绿

附上酷安链接

目录结构

├───node_modules
├───public
├───src //前端相关代码
│   ├───apis
│   ├───assets
│   ├───data
│   ├───pages
│   │   ├───components
│   │   │   ├───article-card
│   │   │   ├───icon-bars
│   │   │   ├───icon-list
│   │   │   └───SwitchRoute
│   │   ├───detail
│   │   │   ├───article-detail
│   │   │   │   └───Comt
│   │   │   │       ├───reply-detail
│   │   │   │       └───time-action
│   │   │   └───person-detail
│   │   └───main
│   │       └───pages
│   │           ├───beautify-page
│   │           ├───fast-news
│   │           ├───head-lines
│   │           ├───hot-list
│   │           ├───tutorial-page
│   │           └───vertical-topic
│   │               └───Comt
│   │                   └───topic-list
│   └───utils
└───src-tauri //后端相关代码

代码书写中的一些问题与抉择

技术选型

React + TypeScript(any大法好) + Less

  • 选React是因为他的灵活性以及(其实就是想熟练一下)
  • TypeScript一开始是想好好写interface的,但是终究还是被我用成了anyscript
  • Less嵌套写法(更好做样式隔离)以及配合组件库做自定义主题
  • 在深思熟虑(想了一小会)后没有选择全局状态管理

是否使用组件库

一开始的想法是需要纯手写界面,但是在对整个App做了一个总体的统揽之后,感觉内容复杂度并不低,所以最终选择了ArcoDesign,这也大大在后面提高了我的开发效率(偷大懒哈哈哈)。

左侧主页Tab的内容组件动态渲染

image.png

本来的想法是定义一个想下面这样的结构来做到一个map函数可以一次性便捷的渲染首页内容:

image.png

通过path字段和懒加载React.lazy来匹配对应的组件,然后就有了下面的这一段代码:

const modules = import.meta.glob('./**')
const Component = (props: any) => {
  console.log(modules)
  const Com = React.lazy(modules[`./pages/${props.path}/index.tsx`] as any)
  console.log(Com)
  return <Com />
}

通过查阅资料,之前使用的webppack可以通过require来动态引入组件,而vite中提供的是import.meta.glob函数,参数为/**代表的是匹配的是目录下及子目录下的所有文件,最终写的能够通过ts的类型检查,但是在最后发现,并不会渲染对应的目录组件,还会导致Component组件无限渲染的。在搜索无果后最终选择了最笨的方法,麻了,又是一次无奈的妥协:

image.png

Tab栏下的对应组件我选择了将其分为多个组件进行封装,目录如下:

image.png

首页卡片组件的封装

在观察中发现,首页中有多个部分是重叠且相似度极高的页面组件,例如下图:

image.png

因此有了组件article-cardicon-list,详细实现移步上面GitHub仓库:

image.png

图片懒加载

酷安作为一个数码社区平台,出现最多的当然是动态中无处不在的图片,因此图片懒加载是项目中不可缺少的一部分。在掘金、思否等搜索过后,大致的实现思路有几种。

  • 首先都要为img设置自定义属性,例如data-src
  • 然后通过各种方案监听图片是否出现在页面可视区域viewport
  • el.offsetTop <= clientHeight + scrollTop + 100
  • getBoundingClientRect()
  • IntersectionObserver API
  • 最后就是我采用的方案
  • img加上attr loading="lazy"浏览器就会自动帮我们实现图片懒加载

页面路由的思考

在我的想法中,我想要的的是在左边页面中,点击动态或者其他模块后,对应的界面都是显示在右边区域中,很容易想象得到的是,右边的模块应该是左边模块的子路由,但是在另外一种情况中,在左边点击后,还会切换左边区域的界面。因此,我想要的的是左边区域与右边区域是两个互不干扰的页面,而非父子路由的嵌套关系,我的做法的使用一个path:"/*"的路由路径,匹配到我的一个自定义组件中,完全由我来工具path的变化来决定左右组件的渲染。也就有了我下面的代码:

const MyApp = () => {
  return useRoutes([
    {
      path: '/',
      element: <App />,
      children: [
        {
          path: '/*',
          element: <SwitchRoute></SwitchRoute>,
        },
      ],
    },
  ])
}
export default MyApp

SwitchRoute组件中,使用useEffect来监听location.pathname来监听路由的变化并更新左右视图,通过getCom函数匹配pathname对应的组件,通过类似下面的代码就能够有区分的渲染左右路由:

const nav = useNavigate()
useEffect(() => {
  nav('main-page/person-detail')
}, [])

具体SwitchRoute组件实现代码如下:

const getCom = (name: string) => {
  switch (name) {
    case 'article-detail':
      return <ArticleDetail></ArticleDetail>
    case 'person-detail':
      return <PersonDetail></PersonDetail>
    case 'main-page':
      return <MainPage></MainPage>
  }
}
const SwitchRoute = (props: any) => {
  const location = useLocation()
  const [comName, setComName] = useState<any[]>([])
  useEffect(() => {
    setComName((names) => {
      const name = location.pathname
        .split('/')
        .filter((_) => _ !== '/')
        .filter((_) => _ !== '')
      return name
    })
  }, [location.pathname])
  return (
    <div className="switch-box">
      {getCom(comName[0])}
      <div className="switch-right">{getCom(comName[1])}</div>
    </div>
  )
}

未完待续

如果文章中有什么出现问题的地方,希望能够得到你的指出并加以改进

后面我还会继续更新项目中的一些想法与思考

🌟Github项目仓库:github.com/ayuan-gy/Co… 求🌟