Based on react18+vite4.x imitating WeChat|react-chat chat room

ReactChat chat IM example | react18.x imitation WeChat App

Vite4.xCreate a chat project based on the latest construction tools react18and use react18+react-dom+react-vant+zustandother technical architectures to implement functions such as sending graphic messages, picture/video previews, red envelopes/moments, etc.

Insert image description here

technical framework

  • Editing tool: vscode
  • Framework technology: react18+react-dom
  • Build tool: vite4.x
  • UI component library: react-vant (Youzan react mobile UI library)
  • Status management: zustand^4.3.9
  • Routing management: react-router-dom^6.14.2
  • className mixed: clsx^2.0.0
  • Pop-up component: rcpop (customized mobile pop-up component based on react18 hooks)
  • Style processing: sass^1.64.1

Insert image description here

Project structure

Insert image description here
The entire react-chat project is developed using react18 hooks function component coding.

Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here

react18 hooks custom bullet box component

The pop-up functions used in the project are all react18.x hookscustom function components RcPop. Integrated msg/alert/dialog/toast及android/iospop-up window effects. Supports 20+ parameters, component + function calling methods.

Insert image description here
If you are interested in developing pop-up windows with react18 hooks, you can check out the following sharing article.
https://blog.csdn.net/yanxinyun1990/article/details/132019347

react18 custom navbar+tabbar component

Insert image description here
Insert image description here
The top navigation bar and bottom menu bar in the project are custom components to implement functions.

<Navbar
    back={false}
    bgcolor="linear-gradient(to right, #139fcc, #bc8bfd)"
    title={<span className="ff-gg">React18-Chat</span>}
    fixed
    right={
        <>
            <i className="iconfont ve-icon-search"></i>
            <i className="iconfont ve-icon-plus-circle-o ml-30"></i>
        </>
    }
/>

Insert image description here

<Tabbar bgcolor="#fefefe" onClick={ 
        handleTabClick} />

main.jsx configuration

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './style.scss'

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

App.jsx main template

import {
    
     HashRouter } from 'react-router-dom'

// 引入路由配置
import Router from './router'

import '@assets/js/fontSize'

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

export default App

react-router-dom v6 routing configuration

Insert image description here

/**
 * react-router-dom路由配置管理
 * andy Q:282310962
*/

import {
    
     lazy, Suspense } from 'react'
import {
    
     useRoutes, Outlet, Navigate } from 'react-router-dom'
import {
    
     Loading } from 'react-vant'

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 Chat = lazy(() => import('@views/chat/chat'))
const ChatInfo = lazy(() => import('@views/chat/info'))
const RedPacket = lazy(() => import('@views/chat/redpacket'))
const My = lazy(() => import('@views/my'))
const Fzone = lazy(() => import('@views/my/fzone'))
const Wallet = lazy(() => import('@views/my/wallet'))
const Setting = lazy(() => import('@views/my/setting'))
const Error = lazy(() => import('@views/404'))

// 加载提示
const SpinLoading = () => {
    
    
  return (
    <div className="rc__spinLoading">
      <Loading size="20" color="#087ea4" vertical textColor="#999">加载中...</Loading>
    </div>
  )
}

// 延迟加载
const lazyload = children => {
    
    
  // React 16.6 新增了<Suspense>组件,让你可以“等待”目标代码加载,并且可以直接指定一个加载的界面
  // 懒加载的模式需要我们给他加上一层 Loading的提示加载组件
  return <Suspense fallback={
    
    <SpinLoading />}>{
    
    children}</Suspense>
}

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

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

// 路由占位模板(类似vue中router-view)
const RouterLayout = () => {
    
    
  return (
    <div className="rc__container flexbox flex-col">
      <Outlet />
    </div>
  )
}

// useRoutes集中式路由配置
export const routerConfig = [
  {
    
    
    path: '/',
    element: lazyload(<RouterAuth><RouterLayout /></RouterAuth>),
    children: [
      // 首页
      // { path: '/', element: <Index /> },
      {
    
     index: true, element: <Index /> },

      // 通讯录模块
      // { path: '/contact', element: lazyload(<Contact />) },
      {
    
     path: '/contact', element: <Contact /> },
      {
    
     path: '/uinfo', element: <Uinfo /> },

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

      // 我的模块
      {
    
     path: '/my', element: <My /> },
      {
    
     path: '/fzone', element: <Fzone /> },
      {
    
     path: '/wallet', element: <Wallet /> },
      {
    
     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 state management Zustand

React18 hooks recommends using the zustand state management plug-in, but of course redux can also be used. zustand is small, easy to use, and its syntax is similar to vue3 pinia syntax.

Insert image description here
Insert image description here

/**
 * Zustand状态管理,配合persist本地持久化存储
*/
import {
    
     create } from 'zustand'
import {
    
     persist, createJSONStorage } from 'zustand/middleware'

export const authStore = create(
    persist(
        (set, get) => ({
    
    
            isLogged: false,
            token: null,
            loggedData: (data) => set({
    
    isLogged: data.isLogged, token: data.token})
        }),
        {
    
    
            name: 'authState',
            // name: 'auth-store', // name of the item in the storage (must be unique)
            // storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
        }
    )
)

Insert image description here

reactChat chat module

Insert image description here
The editor uses the div editable function contentEditable, which supports emoticon insertion at the cursor and multi-line text input.

<div
    {
    
    ...rest}
    ref={
    
    editorRef}
    className={
    
    clsx('editor', className)}
    contentEditable
    onClick={
    
    handleClick}
    onInput={
    
    handleInput}
    onFocus={
    
    handleFocus}
    onBlur={
    
    handleBlur}
    style={
    
    {
    
    'userSelect': 'none', 'WebkitUserSelect': 'none'}}
>
</div>

This input box solves react18 hooks and the cursor will jump to the first place.

/**
 * 编辑器模板
*/
import {
    
     useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import clsx from 'clsx'

const Editor = forwardRef((props, ref) => {
    
    
    const {
    
    
        // 编辑器值
        value = '',

        // 事件
        onClick = () => {
    
    },
        onFocus = () => {
    
    },
        onBlur = () => {
    
    },
        onChange = () => {
    
    },

        className,
        ...rest
    } = props

    const [editorText, setEditorText] = useState(value)
    const editorRef = useRef(null)

    const isChange = useRef(true)
    // 记录光标位置
    const lastCursor = useRef(null)

    // 获取光标最后位置
    const getLastCursor = () => {
    
    
        let sel = window.getSelection()
        if(sel && sel.rangeCount > 0) {
    
    
            return sel.getRangeAt(0)
        }
    }

    const handleInput = () => {
    
    
        setEditorText(editorRef.current.innerHTML)

        lastCursor.current = getLastCursor()
    }
    
    // 点击编辑器
    const handleClick = () => {
    
    
        onClick?.()

        lastCursor.current = getLastCursor()
    }
    // 获取焦点
    const handleFocus = () => {
    
    
        isChange.current = false
        onFocus?.()

        lastCursor.current = getLastCursor()
    }
    // 失去焦点
    const handleBlur = () => {
    
    
        isChange.current = true
        onBlur?.()
    }

    // 删除内容
    const handleDel = () => {
    
    
        let range
        let sel = window.getSelection()
        if(lastCursor.current) {
    
    
            sel.removeAllRanges()
            sel.addRange(lastCursor.current)
        }
        range = getLastCursor()
        range.collapse(false)
        document.execCommand('delete')

        // 删除表情时禁止输入法
        setTimeout(() => {
    
     editorRef.current.blur() }, 0);
    }
    // 清空编辑器
    const handleClear = () => {
    
    
        editorRef.current.innerHTML = ''
    }

    // 光标处插入内容 @param html 需要插入的内容
    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)
        }
    }

    useEffect(() => {
    
    
        if(isChange.current) {
    
    
            setEditorText(value)
        }
    }, [value])

    useEffect(() => {
    
    
        onChange?.(editorText)
    }, [editorText])

    // 暴露指定的方法给父组件调用
    useImperativeHandle(ref, () => ({
    
    
        insertHtmlAtCursor,
        handleDel,
        handleClear
    }))

    return (
        ...
    )
})

export default Editor

OK, here is the example of imitating WeChat chat based on react18+react-vant.

Tauri+Vue3+Pinia2 middle and backend management system

uniapp+vue3 imitates chatgpt chat session function

Insert image description here

Guess you like

Origin blog.csdn.net/yanxinyun1990/article/details/132308526