网站优化预算,网络布线,wordpress手机 主题,平台类网站营销方案一、效果展示 默认效果#xff0c;一开始默认按照最热进行排序 发布了一条评论 按照最新进行排序 按照最新进行排序 二、效果说明
页面上默认有3条评论#xff0c;且一开始进入页面的时候是按照点赞数量进行倒序排列展示#xff0c;可以点击【最热 、最新】进行排序的切换。…一、效果展示 默认效果一开始默认按照最热进行排序 发布了一条评论 按照最新进行排序 按照最新进行排序 二、效果说明
页面上默认有3条评论且一开始进入页面的时候是按照点赞数量进行倒序排列展示可以点击【最热 、最新】进行排序的切换。
在文本框中输入要评论的文本然后点击【发布】按钮即可将评论添加到下方的评论列表当中进行展示如果没有输入任何文本的时候直接点击【发布】按钮会弹出提示对话框。
点击删除按钮可以将对应的评论从评论列表中移除。
三、涉及知识点
3.1 useState
3.1.1 基础使用
useState 是一个 React Hook函数它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果。 语法
const [state, setState] useState(initialState)
useState是一个函数返回值是一个数组 数组中的第一个参数是状态变量第二个参数是set函数用来修改状态变量useState的参数将作为state的初始值 本质
和普通JS变量不同的是状态变量一旦发生变化组件的视图UI也会跟着变化数据驱动视图。 注意事项 useState 是一个 Hook因此你只能在 组件的顶层 或自己的 Hook 中调用它。你不能在循环或条件语句中调用它。如果你需要这样做请提取一个新组件并将状态移入其中。在严格模式中React 将 两次调用初始化函数以 帮你找到意外的不纯性。这只是开发时的行为不影响生产。如果你的初始化函数是纯函数本该是这样就不应影响该行为。其中一个调用的结果将被忽略。 3.1.2 修改状态的规则 状态不可变 在React中状态被认为是只读的我们应该始终 替换它而不是修改它 直接修改状态不能引发视图更新。 修改对象状态 规则对于对象类型的状态变量应该始终传给set方法一个 全新的对象 来进行修改。 3.2 classnames优化类名控制 classnames是一个简单的JS库可以非常方便的 通过条件动态控制class类名的显示。 现在的问题字符串的拼接方式不够直观也容易出错。 3.3 受控表单绑定 概念使用React组件的状态useState控制表单的状态。 1. 准备一个React状态值
const [value, setValue] useState() 2. 通过value属性绑定状态通过onChange属性绑定状态同步的函数 // 通过value属性绑定react状态
// 绑定onChange事件通过事件参数e拿到输入框最新的值反向修改到react状态
inputtypetextvalue{value}onChange{(e) setValue(e.target.value)}/ 3.4 获取DOM
在React组件中获取 / 操作DOM需要使用useRef React Hook钩子函数分为两步
1. 使用useRef创建 ref 对象并与 JSX 绑定 2. 在DOM可用时通过 inputRef.current 拿到 DOM 对象 四、代码实现
4.1 逻辑渲染层
import { useRef, useState } from react;
import ./App.scss
import avatar from ./image/bozai.png
import dayjs from dayjs
import { v4 as uuidV4 } from uuid
import _ from lodash
import classNames from classnames
function App() {// 当前登录用户信息const user {// 用户iduid: 30009257,// 用户头像avatar,// 用户昵称uname: 嘟嘟嘟,}// 评论列表数据const defaultList [{// 评论idrpid: 3,// 用户信息user: {uid: 13258165,avatar: require(./image/panda.jpg),uname: 周杰伦,},// 评论内容content: 哎哟不错哦,// 评论时间ctime: 10-18 08:15,like: 88,},{rpid: 2,user: {uid: 36080105,avatar: require(./image/panda.jpg),uname: 许嵩,},content: 我寻你千百度 日出到迟暮,ctime: 11-13 11:29,like: 88,},{rpid: 1,user: {uid: 30009257,avatar,uname: 黑马前端,},content: 学前端就来黑马,ctime: 10-19 09:00,like: 66,},]// 导航 Tab 数组const tabs [{ type: hot, text: 最热 },{ type: time, text: 最新 },]// 最初的时候先按照最热进行倒叙排列const [remarkList, setRemark] useState(_.orderBy(defaultList, hot, desc))const [content, setContent] useState()const inputRef useRef(null)// 发布评论function handleSubmit() {// 阻止提交空数据if (!content) return alert(请输入评论内容)setRemark([{rpid: uuidV4(),//随机iduser,content,ctime: dayjs(new Date()).format(MM-DD HH:mm),like: 0,},...remarkList])setContent()//清空输入框中的内容inputRef.current?.focus()//重新聚焦}// 删除评论function handleDel(item) {setRemark(remarkList.filter(v v.rpid ! item.rpid))}const [type, setType] useState(hot)/*** tab切换* 1.点击谁就把谁的type记录下来* 2.通过记录的type和每一项遍历时的type做匹配控制激活类名的显示*/function handleTabChange(type) {setType(type)if (type time) {// 按照时间倒序setRemark(_.orderBy(remarkList, ctime, desc))} else {// 按照点赞数倒序setRemark(_.orderBy(remarkList, like, desc))}}return (div classNameAppdiv classNametopdiv classNameleftspan classNamel-title评论/spanspan{remarkList.length}/span/divdiv classNameright{/* 高亮类名 */}{/* classnames优化类名控制classnames是一个简单的JS库可以非常方便的通过条件动态控制class类型的显示*/}{tabs.map((item) {return (spanclassName{classNames(nav-item, { active: type item.type })}key{item.type}onClick{() handleTabChange(item.type)}{item.text}/span)})}/div/divdiv classNamepushimg classNameavatar src{user.avatar} /textarea classNametextarea placeholder发一条友善的评论 ref{inputRef} value{content} onChange{(e) setContent(e.target.value)}/textareabutton classNamepushBtn onClick{handleSubmit}发布/button/divdiv classNamemain{/* 评论项 */}{remarkList.map((item) {return (div classNamem-item key{item.rpid}img classNameavatar src{item.user.avatar} /div classNamemi-rightdiv{item.user.uname}/divdiv classNametext{item.content}/divdiv span{item.ctime}/spanspan classNamelike点赞数{item.like}/spanspan onClick{() handleDel(item)}删除/span/div/div/div)})}/div/div);
}export default App;
4.2 样式层
.App{margin: 10px;
}
.top{margin-bottom: 20px;display: flex;align-items: baseline;font-size: 15px;color: #999;
.left{margin-right: 50px;
.l-title{font-size: 20px;font-weight: 700;color: #000;margin-right: 5px;
}
}
.right{display: flex;flex-direction: row;align-items: center;.nav-item {cursor: pointer;:hover {color: #00aeec;}:last-child::after {display: none;}::after {content: ;display: inline-block;height: 10px;width: 1px;margin: -1px 12px;background-color: #9499a0;}}.nav-item.active{color: #000;}
}
}
.push{margin-bottom: 20px;display: flex;
.textarea{margin: 0 10px;padding: 10px;flex: 1;min-height: 30px;max-height: 100px;border-radius: 10px;border: none;outline: none;background-color: #ebebeb;
}
.pushBtn{width: 100px;height: 50px;font-size: 16px;color: #fff;border: none;border-radius: 5px;background-color: rgba(0,174,236,0.5);:hover{background-color: rgba(0,174,236);}
}
}
.avatar{width: 40px;height: 40px;border-radius: 50%;
}.m-item{display: flex;margin-bottom: 10px;
.mi-right{padding-bottom: 10px;margin-left: 10px;flex: 1;font-size: 14px;color: #999;border-bottom: 1px solid #e4e3e3;
}
.text{margin: 10px 0;color: #000;
}
.like{margin: 0 20px;
}
}