该Demo涉及的技术点
- useState函数(数据驱动视图)
- 子组件的封装
- 条件判断
- 回调函数的封装
1、评论数据
json
{
"list": [
{
"rpid": 3,
"user": {
"uid": "13258165",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "周杰伦"
},
"content": "哎哟,不错哦",
"ctime": "10-18 08: 15",
"like": 126
},
{
"rpid": 2,
"user": {
"uid": "36080105",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "许嵩"
},
"content": "我寻你千百度 日出到迟暮",
"ctime": "11-13 11: 29",
"like": 88
},
{
"rpid": 1,
"user": {
"uid": "30009257",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "前端"
},
"content": "前端真好玩",
"ctime": "10-19 09: 00",
"like": 66
}
]
}
2、具体实现
- 获取json中的list数据 (读取本地文件 / 读取接口)
js
const JsonReader = () => {
// 定义一个数组状态来存储 JSON 数据
const [ commentList, setCommentList ] = useState([]);
// 使用 useEffect 钩子来加载 JSON 文件
useEffect(() => {
// 使用 fetch API 读取 JSON 文件 , 这里需要注意,json文件读取的根路径是public路径
fetch('_db.json')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // 解析 JSON 数据
})
.then(data => {
// console.log(data)
// 将解析后的 JSON 数据存储到 list 数组中
setCommentList(_.orderBy(data.list, 'like', 'desc'));
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}, []); // 空依赖数组表示只在组件挂载时执行一次
return {
commentList,
setCommentList
}
}
// 封装请求数据的Hook
function useGetList () {
// 获取接口数据渲染
const [commentList, setCommentList] = useState([])
useEffect(() => {
// 请求数据
async function getList () {
// axios请求数据
const res = await axios.get(' http://localhost:3004/list')
setCommentList(res.data)
}
getList()
}, [])
return {
commentList,
setCommentList
}
}
- 封装用户信息、导航栏
javascript
// 当前登录用户信息
const user = {
// 用户id
uid: '30009257',
// 用户头像
avatar,
// 用户昵称
uname: '黑马前端',
}
// 导航 Tab 数组
const tabs = [
{ type: 'hot', text: '最热' },
{ type: 'time', text: '最新' },
]
- 封装单个评论组件
javascript
// 封装Item组件
function Item ({ item, onDel }) {
return (
<div className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
src={item.user.avatar}
/>
</div>
</div>
<div className="content-wrap">
{/* 用户名 */}
<div className="user-info">
<div className="user-name">{item.user.uname}</div>
</div>
{/* 评论内容 */}
<div className="root-reply">
<span className="reply-content">{item.content}</span>
<div className="reply-info">
{/* 评论时间 */}
<span className="reply-time">{item.ctime}</span>
{/* 评论数量 */}
<span className="reply-time">点赞数:{item.like}</span>
{/* 条件:user.id === item.user.id */}
{user.uid === item.user.uid &&
<span className="delete-btn" onClick={() => onDel(item.rpid)}>
删除
</span>}
</div>
</div>
</div>
</div>
)
}
- 最终实现
javascript
const App = () => {
// 渲染评论列表
// 1. 使用useState维护list
// const [commentList, setCommentList] = useState(_.orderBy(List, 'like', 'desc'))
// const { commentList, setCommentList } = useGetList()
const { commentList, setCommentList } = JsonReader()
// 删除功能
const handleDel = (id) => {
console.log(id)
// 对commentList做过滤处理
setCommentList(commentList.filter(item => item.rpid !== id))
}
// tab切换功能
// 1. 点击谁就把谁的type记录下来
// 2. 通过记录的type和每一项遍历时的type做匹配 控制激活类名的显示
const [type, setType] = useState('hot')
const handleTabChange = (type) => {
console.log(type)
setType(type)
// 基于列表的排序
if (type === 'hot') {
// 根据点赞数量排序
// lodash
setCommentList(_.orderBy(commentList, 'like', 'desc'))
} else {
// 根据创建时间排序
setCommentList(_.orderBy(commentList, 'ctime', 'desc'))
}
}
// 发表评论
const [content, setContent] = useState('')
const inputRef = useRef(null)
const handlPublish = () => {
setCommentList([
...commentList,
{
rpid: uuidV4(), // 随机id
user: {
uid: '30009257',
avatar,
uname: '黑马前端',
},
content: content,
ctime: dayjs(new Date()).format('MM-DD hh:mm'), // 格式化 月-日 时:分
like: 66,
}
])
// 1. 清空输入框的内容
setContent('')
// 2. 重新聚焦 dom(useRef) - focus
inputRef.current.focus()
}
return (
<div className="app">
{/* 导航 Tab */}
<div className="reply-navigation">
<ul className="nav-bar">
<li className="nav-title">
<span className="nav-title-text">评论</span>
{/* 评论数量 */}
<span className="total-reply">{10}</span>
</li>
<li className="nav-sort">
{/* 高亮类名: active */}
{tabs.map(item =>
<span
key={item.type}
onClick={() => handleTabChange(item.type)}
className={classNames('nav-item', { active: type === item.type })}>
{item.text}
</span>)}
</li>
</ul>
</div>
<div className="reply-wrap">
{/* 发表评论 */}
<div className="box-normal">
{/* 当前用户头像 */}
<div className="reply-box-avatar">
<div className="bili-avatar">
<img className="bili-avatar-img" src={avatar} alt="用户头像" />
</div>
</div>
<div className="reply-box-wrap">
{/* 评论框 */}
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
ref={inputRef}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text" onClick={handlPublish}>发布</div>
</div>
</div>
</div>
{/* 评论列表 */}
<div className="reply-list">
{/* 评论项 */}
{commentList.map(item => <Item key={item.id} item={item} onDel={handleDel} />)}
</div>
</div>
</div>
)
}
最终效果: