适合初学者的 react 小案例

410 阅读6分钟

案例

案例效果

image.png

基本结构

image.png

先搞 index 里边的东西

index.js

import { createRoot } from "react-dom/client";

// 引入组件
import App from "./App";

const root = createRoot(document.querySelector("#root"));

// 使用
root.render(<App />);

使用 scss

如果你要在 react 中使用 scss,需要下载对应的包:

npm i sass

不要下载错了,虽然我们用的是 .scss ,但是要下载的是 sass

App.scss 样式代码

App.scss

* {
  margin: 0;
  padding: 0;
  list-style: none;
}
.App {
  /* width: 1090px; */

  width: 80%;
  margin: 50px auto;
}
.comment-head {
  margin: 0 0 20px;
  font-size: 18px;
  line-height: 24px;
  color: #222;
}

.comment-send {
  margin: 10px 0;
}

.user-face {
  float: left;
  margin: 7px 0 0 5px;
  position: relative;
}

.user-head {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
}

.textarea-container {
  position: relative;
  margin-left: 85px;
  margin-right: 80px;
}
.textarea-container:hover .ipt-txt {
  background-color: #fff;
  border-color: #00a1d6;
}

.ipt-txt {
  font-size: 12px;
  display: inline-block;
  box-sizing: border-box;
  background-color: #f4f5f7;
  border: 1px solid #e5e9ef;
  overflow: auto;
  border-radius: 4px;
  color: #555;
  width: 100% !important;
  height: 65px;
  transition: 0s;
  padding: 5px 10px;
  line-height: normal;
  resize: none;
  outline: none;
}

.comment-submit {
  width: 70px;
  height: 64px;
  position: absolute;
  right: -80px;
  top: 0;
  padding: 4px 15px;
  font-size: 14px;
  color: #fff;
  border-radius: 4px;
  text-align: center;
  min-width: 60px;
  vertical-align: top;
  cursor: pointer;
  background-color: #00a1d6;
  border: 1px solid #00a1d6;
  transition: 0.1s;
  user-select: none;
  outline: none;
}
.comment-submit:hover {
  background-color: #00b5e5;
  border-color: #00b5e5;
}

.comment-emoji {
  padding: 0;
  width: 66px;
  height: 24px;
  color: #99a2aa;
  border: 1px solid #e5e9ef;
  border-radius: 4px;
  position: relative;
  font-size: 12px;
  text-align: center;
  line-height: 23px;
  margin-left: 86px;
  margin-top: 3px;
  cursor: pointer;
  display: inline-block;
}
.comment-emoji:hover {
  color: #6d757a;
}

.face {
  display: inline-block;
  vertical-align: middle;
  line-height: 1;
  width: 16px;
  height: 16px;
  margin-right: 5px;
  background: url("./img/2.jpg") no-repeat -408px -24px;
}
.comment-emoji:hover .face {
  background-position: -472px -24px;
}

.comment-emoji .text {
  display: inline-block;
  vertical-align: middle;
  line-height: 1;
  font-size: 12px !important;
}

.tabs-order {
  margin: 0 0 24px 0;
  border-bottom: 1px solid #e5e9ef;
}

.sort-container {
  display: flex;
}

.tabs-order li {
  background-color: transparent;
  border-radius: 0;
  border: 0;
  padding: 8px 0;
  margin-right: 16px;
  border-bottom: 1px solid transparent;
  position: relative;
  float: left;
  cursor: pointer;
  line-height: 20px;
  height: 20px;
  font-size: 14px;
  font-weight: bold;
  color: #222;
}

.tabs-order li:last-child {
  margin: 0 16px;
}

.tabs-order li.on {
  border-bottom: 1px solid #00a1d6;
  color: #00a1d6;
}

.tabs-order li.on::after {
  content: "";
  width: 6px;
  height: 3px;
  background: transparent url("./img/2.jpg") -669px -31px no-repeat;
  position: absolute;
  bottom: 0;
  left: 50%;
  margin-left: -3px;
  visibility: visible;
}

.list-item {
  display: flex;
}
.list-item:first-child {
  padding-top: 22px;
}

.comment {
  flex: 1;
  position: relative;
  margin-left: 35px;
  padding: 22px 0 14px 0;
  border-top: 1px solid #e5e9ef;
}
.list-item:last-child .comment {
  border-bottom: 1px solid #e5e9ef;
}

.comment .user {
  color: #6d757a;
  font-size: 12px;
  font-weight: bold;
  line-height: 18px;
  padding-bottom: 4px;
  display: block;
  word-wrap: break-word;
  position: relative;
}

.comment .text {
  line-height: 20px;
  padding: 2px 0;
  font-size: 14px;
  text-shadow: none;
  overflow: hidden;
  word-wrap: break-word;
  word-break: break-word;
  white-space: pre-wrap;
}

.info {
  color: #99a2aa;
  line-height: 26px;
  font-size: 12px;
}

.icon {
  cursor: pointer;
  background: url("./img/2.jpg") no-repeat;
}

.time {
  margin-right: 20px;
}

.like {
  cursor: pointer;
  margin-right: 20px;
}

.like > i {
  display: inline-block;
  width: 14px;
  height: 14px;
  vertical-align: text-top;
  margin-right: 5px;
  background-position: -153px -25px;
}
.like:hover > i {
  background-position: -218px -25px;
}
.info .liked > i {
  background-position: -154px -89px;
  border: #00a1d6 2px solid;
}

.hate {
  cursor: pointer;
  margin-right: 15px;
}

.hate > i {
  display: inline-block;
  width: 14px;
  height: 14px;
  vertical-align: text-top;
  margin-right: 5px;
  background-position: -153px -153px;
}

.hate:hover > i {
  background-position: -217px -153px;
}
.info .hated > i {
  background-position: -154px -217px;
  border: #00a1d6 2px solid;
}

.btn-hover {
  padding: 0 5px;
  border-radius: 4px;
  margin-right: 15px;
  cursor: pointer;
  display: inline-block;
}

.btn-hover:hover {
  color: #00a1d6;
  background: #e5e9ef;
}

index.js 用到的数据

现在才开始写代码

index.js

你要用到的数据

 // 数据
  const [moke, setMoke] = useState({
    tabs: [
      {
        id: 1,
        name: "热度",
        type: "hot",
      },
      {
        id: 2,
        name: "时间",
        type: "time",
      },
    ],
    active: "hot",
    list: [
      {
        id: 1,
        author: "唐朝",
        comment: "床前明月光,疑是地上霜",
        time: new Date("2021-12-10 09:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 1,
        avatar:
          "https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp",
      },
      {
        id: 2,
        author: "宋词",
        comment: "大江东去,浪淘尽,千古风流人物",
        time: new Date("2021-12-11 09:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 0,
        avatar:
          "https://p9-passport.byteacctimg.com/img/user-avatar/35e9831939c32731d1f9a2c2ff23a2ea~200x200.awebp?",
      },
      {
        id: 3,
        author: "元曲",
        comment: "枯藤老树昏鸦,小桥流水人家",
        time: new Date("2021-12-11 10:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: -1,
        avatar:
          "https://p3-passport.byteimg.com/img/user-avatar/949162ec762d3993dfd413eaf124a19a~100x100.awebp",
      },
    ],
  });

moment 日期格式化插件

在 moke 数据中有用到日期,这个要单独处理,再此处使用的是 moment 插件

// 日期格式化
  function formattingDate(time) {
    return moment(time).format("YYYY-MM-DD HH:mm:ss");
  }

moment() 创建一个 Moment 对象,表示当前的日期和时间,format('YYYY-MM-DD') 会将日期格式化为 “YYYY-MM-DD” 的形式(例如 “2023-07-25”)。

你还可以使用 Moment.js 的函数来执行各种日期和时间的操作。请参考 Moment.js 文档 获取更多详细信息和示例。

请注意,在你想要使用 Moment.js 的每个组件中都需要引入 Moment.js 库。

需要注意的是,由于 Moment.js 目前被认为是一个传统的项目,你可能考虑使用替代方案,例如 Day.js 或者现代 JavaScript 环境中内置的 Date 对象。

完整的index.js 代码

import { useState } from "react";
import "./App.scss";
// 格式化时间
import moment from "moment/moment";

const App = () => {
  // 数据
  const [moke, setMoke] = useState({
    tabs: [
      {
        id: 1,
        name: "热度",
        type: "hot",
      },
      {
        id: 2,
        name: "时间",
        type: "time",
      },
    ],
    active: "hot",
    list: [
      {
        id: 1,
        author: "唐朝",
        comment: "床前明月光,疑是地上霜",
        time: new Date("2021-12-10 09:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 1,
        avatar:
          "https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp",
      },
      {
        id: 2,
        author: "宋词",
        comment: "大江东去,浪淘尽,千古风流人物",
        time: new Date("2021-12-11 09:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 0,
        avatar:
          "https://p9-passport.byteacctimg.com/img/user-avatar/35e9831939c32731d1f9a2c2ff23a2ea~200x200.awebp?",
      },
      {
        id: 3,
        author: "元曲",
        comment: "枯藤老树昏鸦,小桥流水人家",
        time: new Date("2021-12-11 10:09:00"),
        // 1: 点赞 0:无态度 -1:踩
        attitude: -1,
        avatar:
          "https://p3-passport.byteimg.com/img/user-avatar/949162ec762d3993dfd413eaf124a19a~100x100.awebp",
      },
    ],
  });

  // 日期格式化
  function formattingDate(time) {
    return moment(time).format("YYYY-MM-DD HH:mm:ss");
  }

  return (
    <div className="App">
      <div className="comment-container">
        {/* 评论数 */}
        <div className="comment-head">
          <span>{moke.list.length} 评论</span>
        </div>
        {/* 排序 */}
        <div className="tabs-order">
          <ul className="sort-container">
            {moke.tabs.map((item) => (
              <li
                onClick={() => {
                  setMoke({ ...moke, active: item.type });
                  console.log("切换了排序", item.type, item.time);
                }}
                key={item.id}
                className={moke.active === item.type ? "on" : ""}
              >
                按{item.name}排序
              </li>
            ))}

            {/* <li>按时间排序</li> */}
          </ul>
        </div>

        {/* 添加评论 */}
        <div className="comment-send">
          <div className="user-face">
            <img
              className="user-head"
              src="https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp"
              alt=""
            />
          </div>
          <div className="textarea-container">
            <textarea
              cols="80"
              rows="5"
              placeholder="发条友善的评论"
              className="ipt-txt"
            />
            <button className="comment-submit">发表评论</button>
          </div>
          <div className="comment-emoji">
            <i className="face"></i>
            <span className="text">表情</span>
          </div>
        </div>

        {/* 评论列表 */}
        {moke.list.map((item, index) => {
          return (
            <div className="comment-list" key={item.id}>
              <div className="list-item">
                <div className="user-face">
                  <img className="user-head" src={item.avatar} alt="" />
                </div>
                <div className="comment">
                  <div className="user">{item.author}</div>
                  <p className="text">{item.comment}</p>
                  <div className="info">
                    <span className="time">{formattingDate(item.time)}</span>
                    <span
                      className={`like ${item.attitude === 1 ? "liked" : ""}`}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className={`hate ${item.attitude === -1 ? "hated" : ""}`}
                    >
                      <i className="icon" />
                    </span>
                    <span className="reply btn-hover">删除</span>
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default App;

添加优化功能

增加了删除功能,添加评论功能,判断输入框是否为空自动获取焦点功能,拆分了部分组件

代码示例:

import { useRef, useState } from "react";
import "./App.scss";
// 导入 格式化时间
import formattingDate from "./components/module/formattingDate";
// 导入 数据
import mokeData from "./components/module/contentData";

const App = () => {
  const [moke, setMoke] = useState(mokeData);

  // 点击了发表评论
  const textRef = useRef(null);
  const [value, setValue] = useState("");
  const publish = () => {
    console.log("点击了发表评论", value);

    // 当判断输入框为空时,获取焦点
    // .trim() 用于去除字符串两端的空格
    if (value.trim() === "") return textRef.current.focus();

    // 组装评论数据
    const newData = {
      id: 4,
      author: "大明",
      time: Date.now(),
      attitude: 0,
      avatar:
        "http://qpic.y.qq.com/music_cover/yHVEyKkoR4yXKPS2zkqODa4NPG4PZT8nT29ZUb8WXTThC1xgJmXgVg/300?n=1",
    };

    // 将数据添加到数组
    const newList = [newData, ...moke.list];

    // 更新状态
    setMoke({ ...moke, list: newList });
  };

  // 删除
  const delData = (id) => {
    const delId = moke.list.filter((item) => item.id !== 2);
    // console.log(id, moke.list.id, xid);
    setMoke({ ...moke, list: delId });
  };

  return (
    <div className="App">
      <div className="comment-container">
        {/* 评论数 */}
        <div className="comment-head">
          <span>{moke.list.length} 评论</span>
        </div>
        {/* 排序 */}
        <div className="tabs-order">
          <ul className="sort-container">
            {moke.tabs.map((item) => (
              <li
                onClick={() => {
                  setMoke({ ...moke, active: item.type });
                  console.log("切换了排序", item.type, item.time);
                }}
                key={item.id}
                className={moke.active === item.type ? "on" : ""}
              >
                按{item.name}排序
              </li>
            ))}

            {/* <li>按时间排序</li> */}
          </ul>
        </div>

        {/* 添加评论 */}
        <div className="comment-send">
          <div className="user-face">
            <img
              className="user-head"
              src="https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp"
              alt=""
            />
          </div>
          <div className="textarea-container">
            <textarea
              ref={textRef}
              cols="80"
              rows="5"
              placeholder="发条友善的评论"
              className="ipt-txt"
              value={value}
              onChange={(e) => setValue(e.target.value)}
            />
            <button className="comment-submit" onClick={publish}>
              发表评论
            </button>
          </div>
          <div className="comment-emoji">
            <i className="face"></i>
            <span className="text">表情</span>
          </div>
        </div>

        {/* 评论列表 */}
        {moke.list.map((item, index) => {
          return (
            <div className="comment-list" key={item.id}>
              <div className="list-item">
                <div className="user-face">
                  <img className="user-head" src={item.avatar} alt="" />
                </div>
                <div className="comment">
                  <div className="user">{item.author}</div>
                  <p className="text">{item.comment}</p>
                  <div className="info">
                    <span className="time">{formattingDate(item.time)}</span>
                    <span
                      className={`like ${item.attitude === 1 ? "liked" : ""}`}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className={`hate ${item.attitude === -1 ? "hated" : ""}`}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className={`reply btn-hover ${
                        item.id === 2 ? "" : "btn-hover-vanish"
                      }`}
                      onClick={() => delData(item.id)}
                    >
                      删除
                    </span>
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default App;

1