基于React18+Arco高仿微信网页版聊天实例

react18-webchat网页聊天实例

基于react18 hooks+arco design+zustand+react-router等技术构建网页版聊天实战项目ReactChat。实现了发送emoj表情消息、图片/视频预览、网址解析、红包/朋友圈等功能。

技术栈

  • 编辑器:VScode
  • 技术框架:react18+vite4+react-router-dom+zustand+sass
  • UI组件库:@arco-design/web-react (字节跳动react组件库)
  • 状态管理:zustand^4.4.1
  • 路由管理:react-router-dom^6.15.0
  • className拼合:clsx^2.0.0
  • 对话框组件:rdialog (基于react18 hooks自定义桌面端弹窗组件)
  • 美化滚动条:rscroll (基于react18 hooks自定义虚拟滚动条组件)

react-webchat全部采用react18 hooks规范编码开发。

项目目录结构

vite.config.js

js 复制代码
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import { parseEnv } from './src/utils/env'

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  const viteEnv = loadEnv(mode, '.')
  const env = parseEnv(viteEnv)

  return {
    plugins: [react()],

    // 服务器选项
    server: {
      // 端口
      port: env.VITE_PORT,
      // 是否浏览器自动打开
      open: env.VITE_OPEN,
      // 是否开启https
      https: env.VITE_HTTPS,
      // 代理设置
      proxy: {}
    },
    
    resolve: {
      // 配置路径别名
      alias: {
        '@': resolve('.', 'src'),
        '@assets': resolve('.', 'src/assets'),
        '@components': resolve('.', 'src/components'),
        '@views': resolve('.', 'src/views')
      }
    }
  }
})

main.jsx入口页面

js 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import '@arco-design/web-react/dist/css/arco.css'
import './style.scss'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

App.jsx入口模板

js 复制代码
import { HashRouter } from 'react-router-dom'

// 引入useRoutes集中式路由配置
import Router from './router'

function App() {
	return (
		<>
			<HashRouter>
			  <Router />
			</HashRouter>
		</>
	)
}

export default App

react-router路由管理配置

react-router-dom提供了类似vue router-view占位模板Outlet

ts 复制代码
// 路由占位公共模板
const RouterLayout = () => {
    const authState = authStore()
    return (
        <div className="rc__container flexbox flex-alignc flex-justifyc" style={{'--themeSkin': authState.skin}}>
            <div className="rc__layout flexbox flex-col">
                {/* <div className="rc__layout-header">顶部栏</div> */}
                <div className="rc__layout-body flex1 flexbox">
                    {/* 菜单栏 */}
                    <Menu />

                    {/* 中间栏 */}
                    <Aside />

                    {/* 主内容区 */}
                    <div className="rc__layout-main flex1 flexbox flex-col">
                        { lazyload(<Outlet />) }
                    </div>
                </div>
            </div>
        </div>
    )
}

完整的路由配置文件。

ts 复制代码
/**
 * 路由配置 by YXY Q:282310962
*/

import { lazy, Suspense } from 'react'
import { useRoutes, Outlet, Navigate } from 'react-router-dom'
import { Spin } from '@arco-design/web-react'

import { authStore } from '@/store/auth'

// 引入路由页面
import Login from '@views/auth/login'
import Register from '@views/auth/register'
const Index = lazy(() => import('@views/index'))
const Contact = lazy(() => import('@views/contact'))
const Uinfo = lazy(() => import('@views/contact/uinfo'))
const NewFriend = lazy(() => import('@views/contact/newfriend'))
const Chat = lazy(() => import('@views/chat/chat'))
const ChatInfo = lazy(() => import('@views/chat/info'))
const RedPacket = lazy(() => import('@views/chat/redpacket'))
const Fzone = lazy(() => import('@views/my/fzone'))
const Favorite = lazy(() => import('@views/my/favorite'))
const Setting = lazy(() => import('@views/my/setting'))
const Error = lazy(() => import('@views/404'))

import Menu from '@/layouts/menu'
import Aside from '@/layouts/aside'

// 加载提示
const SpinLoading = () => {
    return (
        <div className="rcLoading">
            <Spin size="20" tip='loading...' />
        </div>
    )
}

// 延迟加载
const lazyload = children => {
    // React 16.6 新增了<Suspense>组件,让你可以"等待"目标代码加载,并且可以直接指定一个加载的界面,让它在用户等待的时候显示
    // 路由懒加载报错:react-dom.development.js:19055 Uncaught Error: A component suspended while responding to synchronous input.
    // 懒加载的模式需要我们给他加上一层 Loading的提示加载组件
    return <Suspense fallback={<SpinLoading />}>{children}</Suspense>
}

// 路由鉴权验证
const RouterAuth = ({ children }) => {
    const authState = authStore()

    return authState.isLogged ? (
        children
    ) : (
        <Navigate to="/login" replace={true} />
    )
}

export const routerConfig = [
    {
        path: '/',
        element: <RouterAuth><RouterLayout /></RouterAuth>,
        children: [
            // 首页
            { index: true, element: <Index /> },

            // 通讯录模块
            { path: '/contact', element: <Contact /> },
            { path: '/uinfo', element: <Uinfo /> },
            { path: '/newfriend', element: <NewFriend /> },

            // 聊天模块
            { path: '/chat', element: <Chat /> },
            { path: '/chatinfo', element: <ChatInfo /> },
            { path: '/redpacket', element: <RedPacket /> },

            // 我的模块
            { path: '/fzone', element: <Fzone /> },
            { path: '/favorite', element: <Favorite /> },
            { path: '/setting', element: <Setting /> },

            // 404模块 path="*"不能省略
            { path: '*', element: <Error /> }
        ]
    },
    // 登录/注册
    { path: '/login', element: <Login /> },
    { path: '/register', element: <Register /> }
]

const Router = () => useRoutes(routerConfig)

export default Router

react18新状态管理库Zustand

react-webchat使用了react18 hooks新状态管理插件Zustand。

js 复制代码
/**
 * react18状态管理库Zustand
*/
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const authStore = create(
    persist(
        (set, get) => ({
            isLogged: false,
            token: null,
            // 折叠侧边栏
            collapse: false,
            // 个性换肤
            skin: null,
            // 登录数据
            loggedData: (data) => set({isLogged: data.isLogged, token: data.token}),
            setCollapse: (v) => set({collapse: v}),
            setSkin: (v) => set({skin: v})
        }),
        {
            name: 'authState',
            // name: 'auth-store', // name of the item in the storage (must be unique)
            // storage: createJSONStorage(() => sessionStorage), // by default, 'localStorage'
        }
    )
)

如上图:聊天编辑器支持文字+emoj表情混合、多行文本换行。

js 复制代码
return (
    <div
        {...rest}
        ref={editorRef}
        className={clsx('editor', className)}
        contentEditable
        onClick={handleClick}
        onInput={handleInput}
        onFocus={handleFocus}
        onBlur={handleBlur}
        style={{'userSelect': 'text', 'WebkitUserSelect': 'text'}}
    />
)
js 复制代码
const insertHtmlAtCursor = (html) => {
    let sel, range
    if(!editorRef.current.childNodes.length) {
        editorRef.current.focus()
    }

    if(window.getSelection) {
        // IE9及其它浏览器
        sel = window.getSelection()

        // ##注意:判断最后光标位置
        if(lastCursor.current) {
            sel.removeAllRanges()
            sel.addRange(lastCursor.current)
        }

        if(sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0)
            range.deleteContents()
            let el = document.createElement('div')
            el.appendChild(html)
            var frag = document.createDocumentFragment(), node, lastNode
            while ((node = el.firstChild)) {
                lastNode = frag.appendChild(node)
            }
            range.insertNode(frag)
            if(lastNode) {
                range = range.cloneRange()
                range.setStartAfter(lastNode)
                range.collapse(true)
                sel.removeAllRanges()
                sel.addRange(range)
            }
        }
    } else if(document.selection && document.selection.type != 'Control') {
        // IE < 9
        document.selection.createRange().pasteHTML(html)
    }

    // 执行输入操作
    handleInput()
}

基于react18+arco开发网页版聊天项目就先分享到这里。

juejin.cn/post/724934...

juejin.cn/post/725587...

相关推荐
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码7 小时前
1.
react.js·node.js·angular.js
伍哥的传说8 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
Misha韩9 小时前
React Native 一些API详解
react native·react.js
小李飞飞砖9 小时前
React Native 组件间通信方式详解
javascript·react native·react.js
小李飞飞砖9 小时前
React Native 状态管理方案全面对比
javascript·react native·react.js
202612 小时前
11. vite打包优化
前端·javascript·vite