实现chatgpt打字效果

尽可能的优化,避免长聊天记录导致的卡顿
拆分组件,隔离组件状态渲染,可快速多次重复提交数据
index.js



import React, { useContext, useState, useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import chatgpt from './chatgpt.png';
import user from './user.png';
import { useEffect } from 'react';
import { PageRouterContext } from "../../App";
import config from "../../config/config";
import axios from 'axios';

export default function App1() {
  const [comments, setComments] = useState([]);
  const [userName, setUserName] = useState("");
  const list_container_id = useRef(null);
  const [count, setCount] = useState(0);

  const changeRoute = useContext(PageRouterContext);
  const navigateTo = (changeRoute, id) => {
    changeRoute({ id: id });
  };

  const getList = (title) => {
    return new Promise((resolve) => {
      axios.post('/search/send', {
        message: title
      }).then((response) => {
        if (Array.isArray(response.data.choices)) {
          console.log('请求成功', response);
          resolve(response.data.choices);
        } else {
          alert('程序错误');
        }
        // 请求成功
      }).catch((error) => {
        // 请求失败,
        console.log(error);
      });
    })
  }
  const scrollBottom = () => {
    if (!list_container_id.current) {
      return;
    }
    setTimeout(() => {
      list_container_id.current.scrollTop = list_container_id.current.scrollHeight
    }, 0);
  }

  const updateScroll = useCallback(() => {
    scrollBottom()
  })

  const addComment = async (e) => {
    if (userName.trim() === '') {
      alert('请输入问题');
      return;
    }
    let index = comments.length;
    comments.push({
      id: Math.random(),
      name: userName,
      contents: []
    });
    setComments(comments);
    setCount(count + 1);
    setTimeout(async () => {
      let responseList = await getList(userName);
      comments[index].contents = responseList;
      setComments(comments);
      setUserName('');
      setCount(0);
    }, 0);
  }

  const renderList = () => {
    return comments.length === 0 ?
      (<div className='no-comment'>暂无问题,快去提问吧~</div>)
      : (
        <div
          ref={(el) => {
            list_container_id.current = el;
          }}
          id="list_container_id"
          className="list_container"
        >
          <ul style={{ color: 'white' }}>
            {comments.map((item, index) => (
              <li key={item.id} style={{ color: 'white' }}>
                {
                  item.name ? (
                    <div
                      className='quiz'>
                      <img className='quiz_avatar' src={user} />
                      <span style={{ marginLeft: 8 }}>提问: {item.name}</span>
                    </div>
                  ) : null
                }
                {
                  item.contents.length ? (
                    <div
                      className='answer'>
                      <img className='quiz_avatar' src={chatgpt} />
                      <ClickFingerTextBoard dataList={item.contents} index={index} updateScroll={updateScroll} />
                    </div>
                  ) : <div className='answer'>加载中...</div>
                }
              </li>
            ))}
          </ul>
        </div>
      )
  }
  const handleForm = (e) => {
    setUserName(e.target.value)
  }


  // componentDidUpdate() {
  //   this.scrollBottom()
  // }
  useEffect(() => {
    scrollBottom()
  })

  const back = () => {
    navigateTo(changeRoute, config.pages.home);
  }

  // const { userName } = this.state;
  return (
    <div className='app_container'>
      <div className='no-comment'>
        <button onClick={back} className="confirm_button">返回</button>
      </div>
      {renderList()}
      <div className='input_style'>
        <input
          className='input_quertion'
          type="text"
          placeholder="请输入问题"
          value={userName}
          name="userName"
          onChange={handleForm}
        />
        <div style={{ width: '1%' }}></div>
        <button onClick={addComment} className="confirm_button">发起提问</button>
      </div>
    </div>
  )

}

const ClickFingerTextBoard = React.memo(({ dataList, index, updateScroll }) => {
  console.log('组件' + index + "更新");
  const [list, setList] = useState(dataList);
  const [count, setCount] = useState(1);
  let innerText = useRef([])

  const delay = (time) => {
    return new Promise((resolve) => {
      let timers = setInterval(() => {
        clearInterval(timers);
        resolve();
      }, time);
    })
  };

  useEffect(() => {
    let newList = [];
    let timer1 = null;
    let timer2 = null;
    const calculatedFigures = async () => {
      list.map((item) => {
        newList.push(item.text.split(''));
      })
      timer1 = setTimeout(async () => {
        for (let i = 0; i < newList.length; i++) {
          innerText.current.push([]);
          for (let j = 0; j <= newList[i].length; j++) {
            if (newList[i][j] === undefined) {
              continue;
            }
            await delay(Math.random() * 100);
            innerText.current[i] = innerText.current[i] + newList[i][j];
            updateScroll();
          }
        }
        setTimeout(() => {
          clearTimeout(timer1);
          clearInterval(timer2);
        }, 1000)
      }, 0);
    }

    if (list && list.length) {
      calculatedFigures();
      timer2 = setInterval(() => {
        setCount((count) => count + 1);
      }, 100)
    }
    return () => {
      clearTimeout(timer1);
      clearInterval(timer2);
    }
  }, [])
  return <div>{
    innerText.current.length && innerText.current.map((text, index) => {
      return <div style={{ marginLeft: 8, marginBottom: 10 }} key={index}>回答: <pre style={{ width: "100%" }}>{text}</pre></div>
    })
  }</div>

})


index.css

body,
html {
  margin: 0;
}

ul,
li,
p {
  padding: 0;
  margin: 0;
  list-style: none
}

h3 {
  margin-bottom: 0;
}

.input_quertion {
  width: 63%;
  height: 50px;
  border-radius: 10px;
  border: 1px solid black;
  padding-left: 20px;
}


.content {
  width: 280px;
  margin: 5px;
  border: 1px solid black;
}

.no-comment {
  text-align: center;
  padding: 20px;
  color: white;
  background-color: rgb(53, 54, 65);
}

.app_container {
  width: 100%;
  height: 100%;
  background-color: rgb(53, 54, 65);
}

.confirm_button {
  width: 32%;
  border-radius: 10px;
  background-color: #03b96b;
  border: 0;
  height: 50px;
  color: white;
}

.list_container {
  overflow: auto;
  max-height: calc(100vh - 100px);
}

.input_style {
  width: 100%;
  position: fixed;
  display: flex;
  bottom: 0;
  padding: 1%;
}

.quiz {
  display: flex;
  height: 60px;
  padding: 10px 40px 10px;
  align-items: center;
  color: white;
  line-height: 41px;
  background-color: rgb(53, 54, 65);
}

.quiz_avatar {
  width: 30px;
  height: 30px;
}

.answer {
  display: flex;
  background-color: #3b3d53;
  color: white;
  height: auto;
  line-height: 35px;
  padding: 20px 40px;
  overflow: auto;
  white-space: normal;
  word-break: break-all;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值