黑马React18: 基础Part 1

黑马React: 基础1

Date: November 15, 2023
Sum: React介绍、JSX、事件绑定、组件、useState、B站评论
Tags: 二刷


日志:

11/20: 优化B站评论的案例实现, 增加基础结构和最终结果


React介绍

概念: React由Meta公司研发,是一个用于 构建Web和原生交互界面的库

Untitled

优势: 1-组件化的开发方式 2-优秀的性能 3-丰富的生态 4-跨平台开发




开发环境搭建

使用create-react-app快速搭建开发环境

create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用

执行命令

npx create-react-app react-basic
  1. npx Node.js工具命令,查找并执行后续的包命令
  2. create-react-app 核心包(固定写法),用于创建React项目
  3. react-basic React项目的名称(可以自定义)

之后切入到文件夹下, 使用 npm start启动项目

Untitled

扫描二维码关注公众号,回复: 17183363 查看本文章

创建React项目的更多方式: https://zh-hans.react.dev/learn/start-a-new-react-project

工作方式:

Untitled

理解:

index.js 项目入口
App.js 项目的根组件
index.html html文件

项目的根组件以React组件的方式渲染到index.html中

  • Code:

    index.js 简化后的内容

    // 项目的入口 从这里开始
    
    // React必要的两个核心包
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    
    // 导入项目的根组件
    import App from './App';
    
    // 把App根组件渲染到id为root的dom节点上
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <App />
    );
    



JSX基础-概念和本质

什么是JSX

**概念:**JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式

Untitled

优势:

  1. HTML的声明式模版写法 2. JS的可编程能力

JSX的本质

JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行

Untitled



JSX基础-高频场景

JSX中使用JS表达式

在JSX中可以通过 大括号语法{} 识别 JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

  1. 使用引号传递字符串
  2. 使用JavaScript变量
  3. 函数调用和方法调用
  4. 使用JavaScript对象

注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中

案例:

  • Code:

    // 项目的根组件
    // App -> index.js -> public/index.html(root)
    
    const count = 100
    
    function getName() {
      return 'jack'
    }
    
    function App() {
      return (
        <div className="App">
          this is App
          {/* 使用引号传递字符串 */}
          {'this is message'}
          {/* 识别js变量 */}
          {count}
          {/* 函数调用 */}
          {getName()}
          {/* 方法调用 */}
          {new Date().getDate()}
          {/* 使用js对象 */}
          <div style={
         
         { color: 'red'}}>this is div</div>
        </div>
      )
    }
    
    export default App
    

效果:

Untitled


JSX中实现列表渲染

**语法:**在JSX中可以使用原生JS中的map方法遍历渲染列表

Untitled

案例:

const list = [
  { id: 1001, name: 'Vue'},
  { id: 1002, name: 'React'},
  { id: 1003, name: 'Angular'}
]

function App() {
  return (
    <div className="App">
      this is App
      {/* 渲染列表 */}
      <ul>
        {/* { list.map(item => <li>Vue</li>) } */}
        { list.map(item => <li key={item.id}>{ item.name }</li>) }
      </ul>
    </div>
  )
}

export default App

效果:

Untitled

要点:

1-key值绑定

作用: 提升性能

方式: 每一项要加上一个独一无二的key值


JSX中实现条件渲染

Untitled

**语法:**在React中,可以通过逻辑与运算符&&、三元表达式(?:)实现基础的条件渲染

案例:

const isLogin = true

function App() {
  return (
    <div className="App">
      {/* 逻辑与 && */}
      { isLogin && <span>this is span</span>}

      {/* 三目运算符 */}
      { isLogin ? <span>this is span</span> : <div>this is div</div>}

      {/* 逻辑或 || */}
      { isLogin || <span>this is span</span>}
    </div>
  )
}

export default App

效果:

this is spanthis is span


JSX中实现复杂条件渲染

Untitled

需求:列表中需要根据文章状态适配三种情况,单图,三图,和无图三种模式

解决方案:自定义函数 + if判断语句

案例:

// 定义文章类型
const articleType = 3  // 0 1 3

// 定义核心函数(根据文章类型返回不同的JSX模版)

function getArticleTem () {
  if (articleType === 0) {
    return <div>我是无图文章</div>
  } else if (articleType === 1) {
    return <div>我是单图模式</div>
  } else {
    return <div>我是三图模式</div>
  }
}

function App () {
  return (
    <div className="App">
      {/* 调用函数渲染不同的模版 */}
      {getArticleTem()}
    </div>
  )
}

export default App

效果:

我是三图模式




React中的事件绑定

React 基础事件绑定

**语法:**on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

Code:

function App() {
  const handleClick = () => {
    console.log('button被点击了');
  }
  return (
    <div className="App">
      <button onClick={handleClick}>Click</button>
    </div>
  )
}

export default App

Res:

Untitled


使用事件对象参数

语法:在事件回调函数中设置形参e

Code:


function App() {
  const handleClick = (e) => {
    console.log('button被点击了', e);
  }
  return (
    <div className="App">
      <button onClick={handleClick}>Click</button>
    </div>
  )
}

export default App

Res:

Untitled


传递自定义参数

语法:事件绑定的位置改成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参

Code:

function App() {
  const handleClick = (name) => {
    console.log('button被点击了', name);
  }
  return (
    <div className="App">
      <button onClick={() => handleClick('jack')}>Click</button>
    </div>
  )
}

export default App

Res:

Untitled

注意:不能直接写函数调用,这里事件绑定需要一个函数引用


同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应

Code:

function App() {
  const handleClick = (name, e) => {
    console.log('button被点击了', name, e);
  }
  return (
    <div className="App">
      <button onClick={(e) => handleClick('jack', e)}>Click</button>
    </div>
  )
}

export default App

Res:

Untitled




React中的组件

组件是什么

概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次

Untitled

组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用



React组件

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可

Code:

function Button() {
  return <button>click me!</button> 
}

function App() {
  return (
    <div className="App">
      {/* 自闭合 */}
      <Button />
      {/* 成对标签 */}
      <Button></Button>
    </div>
  )
}

export default App

Res:

Untitled




useState

useState基础使用

概念:

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果

Untitled

本质:和普通变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)

const [count, setCount] = useState(0)

1- useState是一个函数, 返回值是一个数组

2-数组中的第一个参数是状态变量, 第二个参数是set函数用来修改状态变量

3-useState的参数将作为count的初始值

Case:

func: 点击按钮, 数值不断增加

Code:

import { useState } from "react"

function App() {
  // 1. 调用 useState 添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [count, setCount] = useState(0)

  // 2. 点击事件
  const handleClick = () => {
    setCount(count + 1)
  }
  return (
    <div className="App">
      <button onClick={ () => handleClick()}>Add-{count}</button>
    </div>
  )
}

export default App

Res:

Untitled



修改状态的规则

状态不可变

在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新

Untitled

Untitled

Case:

Code:

import { useState } from "react"

function App() {
  // 1. 调用 useState 添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [count, setCount] = useState(0)

  // 2. 点击事件
  const handleClick = () => {
    setCount(count + 1)
  }
  return (
    <div className="App">
      <button onClick={ () => handleClick()}>Add-{count}</button>
    </div>
  )
}

export default App


修改对象状态

规则:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改

直接修改原对象,不引发视图变化

Untitled

调用set传入新对象用于修改

Untitled

理解: 这里先用展开运算符做个拷贝, 然后再用后面重复属性进行替换即可.

Case:

func: 点击按变换名字

Code:

import { useState } from "react"

function App() {

  const [form, setForm] = useState({ name: 'jack' })

  const handleClick = () => {
    setForm({
      ...form,
      name: 'rose'
    })
  }
  return (
    <div className="App">
      <button onClick={ () => handleClick()}>Add-{form.name}</button>
    </div>
  )
}

export default App

Res:

Untitled

复习知识点:

1-…展开匀速符: 深浅拷贝问题

展开运算符使用的对象如果只是针对简单的一级基础数据,就是深拷贝;
展开运算符使用的对象内容包含二级或更多的复杂的数据,那就是浅拷贝;




组件的样式处理

组件基础样式方案

React组件基础的样式控制有俩种方式

  1. 行内样式(不推荐)

Untitled

  1. class类名控制

Untitled

Case:

Code:

index.js

import './css/index.css'

const style = {
  color: 'red',
  fontSize: '20px'
}

function App() {
  return (
    <div className="App">
      {/* 行内样式控制 */}
      <div style={
   
   { color: 'red'}}>this is span</div>

      {/* 内部样式控制 */}
      <div style={style}>this is span</div>

      {/* 外部样式控制 */}
      <span className='outer'>this is span</span>
    </div>
  )
}

export default App

index.css

.outer {
  background-color: blue;
}

Untitled




案例:B站评论

效果展示:

Untitled

  1. 渲染评论列表
  2. 删除评论实现
  3. 渲染导航Tab和高亮实现
  4. 评论列表排序功能实现

项目基础结构:

  • Code: App.js

    import './App.scss'
    import avatar from './images/bozai.png'
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端',
    }
    
    // 导航 Tab 数组
    const tabs = [
      { type: 'hot', text: '最热' },
      { type: 'time', text: '最新' },
    ]
    
    const App = () => {
    
      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">
              </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="发一条友善的评论"
                />
                {/* 发布按钮 */}
                <div className="reply-box-send">
                </div>
              </div>
            </div>
            {/* 评论列表 */}
            <div className="reply-list">
              {/* 评论项 */}
            </div>
          </div>
        </div>
      )
    }
    
    export default App
    

注: 里面代码需要微调一下, 具体可以结合视频里的代码



渲染评论列表

核心思路

  1. 使用useState维护评论列表
  2. 使用map方法对列表数据进行遍历渲染(别忘了加key)

key Code:

const [commentList, setCommentList] = useState([])
<div className="reply-list">
    {/* 评论项 */}
    {commentList.map(item => <Item key={item.id} item={item} onDel={handleDel} />)}
  </div>
</div>
  • Code: App.js

    import { useState } from 'react'
    import './App.scss'
    import avatar from './images/bozai.png'
    
    // 评论列表数据
    const list = [
      {
        // 评论id
        rpid: 3,
        // 用户信息
        user: {
          uid: '13258165',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '周杰伦'
        },
        // 评论内容
        content: '哎呦, 不错哦',
        // 评论事件
        ctime: '10-18 08:15',
        like: 88,
      },
    ]
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端',
    }
    
    // 导航 Tab 数组
    const tabs = [
      { type: 'hot', text: '最热' },
      { type: 'time', text: '最新' },
    ]
    
    // 渲染评论列表
    // 1. 使用 useState 维护list
    
    const App = () => {
      const [commetList, setCommetList] = useState(list)
      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">
              </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="发一条友善的评论"
                />
                {/* 发布按钮 */}
                <div className="reply-box-send">
                </div>
              </div>
            </div>
            {/* 评论列表 */}
            <div className="reply-list">
              {/* 评论项 */}
              {commetList.map(item => (
                <div key={item.rpid} 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 */}
                        <span className="delete-btn" >
                          删除
                        </span>
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )
    }
    
    export default App
    


实现评论删除

需求:

  1. 只有自己的评论才显示删除按钮
  2. 点击删除按钮,删除当前评论,列表中不再显示

核心思路

  1. 删除显示 - 条件渲染 2. 删除功能 - 拿到当前项id以id为条件对评论列表做filter过滤

handleDel实现列表过滤

// 删除功能
const handleDel = (id) => {
  console.log(id)
  // 对commentList做过滤处理
  setCommentList(commentList.filter(item => item.rpid !== id))
}

匹配删除 onDel

{/* 条件:user.id === item.user.id */}
{ user.uid === item.user.uid && (
  <span className="delete-btn" onClick={() => handleDel(item.rpid)}>
    删除
  </span>
)}
  • Code: App.js

    import { useState } from 'react'
    import './App.scss'
    import avatar from './images/bozai.png'
    
    // 评论列表数据
    const list = [
      {
        // 评论id
        rpid: 1,
        // 用户信息
        user: {
          uid: '13258165',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '周杰伦'
        },
        // 评论内容
        content: '哎呦, 不错哦',
        // 评论事件
        ctime: '10-18 08:15',
        like: 88,
      },
      {
        // 评论id
        rpid: 2,
        // 用户信息
        user: {
          uid: '12258164',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '许嵩'
        },
        // 评论内容
        content: '我寻你千百度',
        // 评论事件
        ctime: '10-18 10:15',
        like: 88,
      },
      {
        // 评论id
        rpid: 3,
        // 用户信息
        user: {
          uid: '30009257',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '黑马 '
        },
        // 评论内容
        content: '来黑马!',
        // 评论事件
        ctime: '10-19 09:15',
        like: 66,
      }, 
    ]
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端',
    }
    
    // 导航 Tab 数组
    const tabs = [
      { type: 'hot', text: '最热' },
      { type: 'time', text: '最新' },
    ]
    
    const App = () => {
      // 渲染评论列表
      // 1. 使用 useState 维护list
      const [commetList, setCommetList] = useState(list)
      
      // 2. 删除评论
      const handleDel = (id) => {
        console.log(id);
        // 对commetList做过滤处理
        setCommetList(commetList.filter(item => item.rpid !== id))
      }
      
      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">
              </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="发一条友善的评论"
                />
                {/* 发布按钮 */}
                <div className="reply-box-send">
                </div>
              </div>
            </div>
            {/* 评论列表 */}
            <div className="reply-list">
              {/* 评论项 */}
              {commetList.map(item => (
                <div key={item.rpid} 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={() => handleDel(item.rpid)}>
                            删除
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )
    }
    
    export default App
    


渲染Tab+点击高亮实现

需求:点击哪个tab项,哪个做高亮处理

Untitled

核心思路

点击谁就把谁的type(独一无二的标识)记录下来,然后和遍历时的每一项的type做匹配,谁匹配到就设置负责高亮的类名

Code:

// 导航 Tab 数组
const tabs = [
  { type: 'hot', text: '最热' },
  { type: 'time', text: '最新' },
]

 ...

// 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'))
  }
}

...

<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>

关键点:

复杂样式的写法

className={classNames('nav-item', {active: type === item.type})}
  • Code:

    import { useState } from 'react'
    import './App.scss'
    import avatar from './images/bozai.png'
    import classNames from 'classnames'
    
    // 评论列表数据
    const list = [
      {
        // 评论id
        rpid: 1,
        // 用户信息
        user: {
          uid: '13258165',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '周杰伦'
        },
        // 评论内容
        content: '哎呦, 不错哦',
        // 评论事件
        ctime: '10-18 08:15',
        like: 88,
      },
      {
        // 评论id
        rpid: 2,
        // 用户信息
        user: {
          uid: '12258164',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '许嵩'
        },
        // 评论内容
        content: '我寻你千百度',
        // 评论事件
        ctime: '10-18 10:15',
        like: 88,
      },
      {
        // 评论id
        rpid: 3,
        // 用户信息
        user: {
          uid: '30009257',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '黑马 '
        },
        // 评论内容
        content: '来黑马!',
        // 评论事件
        ctime: '10-19 09:15',
        like: 66,
      }, 
    ]
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端',
    }
    
    // 导航 Tab 数组
    const tabs = [
      { type: 'hot', text: '最热' },
      { type: 'time', text: '最新' },
    ]
    
    const App = () => {
      // 渲染评论列表
      // 1. 使用 useState 维护list
      const [commetList, setCommetList] = useState(list)
      
      // 2. 删除评论
      const handleDel = (id) => {
        console.log(id);
        // 对commetList做过滤处理
        setCommetList(commetList.filter(item => item.rpid !== id))
      }
    
      // tab切换功能
      // 1. 点击谁就把谁的type记录下来
      // 2. 通过记录的type值去过滤评论列表 控制激活类名的显示
      const [type, setType] = useState('hot')
      const handleTabChange = (type) => {
        // console.log(type);
        setType(type)
      }
    
      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)}
                    // Plan1:
                    className={classNames('nav-item', {active: type === item.type})}
                    // Plan2:
                    // className={`nav-item ${type === item.type && 'active'}`}
                  >{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="发一条友善的评论"
                />
                {/* 发布按钮 */}
                <div className="reply-box-send">
                </div>
              </div>
            </div>
            {/* 评论列表 */}
            <div className="reply-list">
              {/* 评论项 */}
              {commetList.map(item => (
                <div key={item.rpid} 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={() => handleDel(item.rpid)}>
                            删除
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )
    }
    
    export default App
    


排序功能实现

Untitled

需求:

点击最新,评论列表按照创建时间倒序排列(新的在前),点击最热按照点赞数排序(多的在前)

**核心思路:**把评论列表状态数据进行不同的排序处理,当成新值传给set函数重新渲染视图UI

Code:

method: 采用 lodash 来进行排序处理

// 基于列表的排序
if (type === 'hot') {
  // 根据点赞数量排序 
  // lodash
  setCommentList(_.orderBy(commentList, 'like', 'desc'))
} else {
  // 根据创建时间排序
  setCommentList(_.orderBy(commentList, 'ctime', 'desc'))
}

拓展:

如果你想要一开始就是最热的模式, 那么在useState的时候就可以设定

const [commetList, setCommetList] = useState(_.orderBy(list, 'like', 'desc'))
  • Code:

    import { useState } from 'react'
    import './App.scss'
    import avatar from './images/bozai.png'
    import classNames from 'classnames'
    import _ from 'lodash'
    
    // 评论列表数据
    const list = [
      {
        // 评论id
        rpid: 1,
        // 用户信息
        user: {
          uid: '13258165',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '周杰伦'
        },
        // 评论内容
        content: '哎呦, 不错哦',
        // 评论事件
        ctime: '10-18 08:15',
        like: 99,
      },
      {
        // 评论id
        rpid: 2,
        // 用户信息
        user: {
          uid: '12258164',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '许嵩'
        },
        // 评论内容
        content: '我寻你千百度',
        // 评论事件
        ctime: '10-18 10:15',
        like: 55,
      },
      {
        // 评论id
        rpid: 3,
        // 用户信息
        user: {
          uid: '30009257',
          avatar: 'http://toutiao.itheima.net/resources/images/98.jpg',
          uname: '黑马 '
        },
        // 评论内容
        content: '来黑马!',
        // 评论事件
        ctime: '10-19 09:15',
        like: 66,
      }, 
    ]
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端',
    }
    
    // 导航 Tab 数组
    const tabs = [
      { type: 'hot', text: '最热' },
      { type: 'time', text: '最新' },
    ]
    
    const App = () => {
      // 渲染评论列表
      // 1. 使用 useState 维护list
      const [commetList, setCommetList] = useState(_.orderBy(list, 'like', 'desc'))
      
      // 2. 删除评论
      const handleDel = (id) => {
        console.log(id);
        // 对commetList做过滤处理
        setCommetList(commetList.filter(item => item.rpid !== id))
      }
    
      // tab切换功能
      // 1. 点击谁就把谁的type记录下来
      // 2. 通过记录的type值去过滤评论列表 控制激活类名的显示
      const [type, setType] = useState('hot')
      const handleTabChange = (type) => {
        console.log(type); 
        setType(type)
        // 基于列表的排序
        if(type === 'hot') {
          // 根据点赞数排序
          setCommetList(_.orderBy(commetList, 'like', 'desc'))
        }else {
          // 根据创建时间排序
          setCommetList(_.orderBy(commetList, 'ctime', 'desc'))
        }
      }
    
      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)}
                    // Plan1:
                    className={classNames('nav-item', {active: type === item.type})}
                    // Plan2:
                    // className={`nav-item ${type === item.type && 'active'}`}
                  >{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="发一条友善的评论"
                />
                {/* 发布按钮 */}
                <div className="reply-box-send">
                </div>
              </div>
            </div>
            {/* 评论列表 */}
            <div className="reply-list">
              {/* 评论项 */}
              {commetList.map(item => (
                <div key={item.rpid} 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={() => handleDel(item.rpid)}>
                            删除
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )
    }
    
    export default App
    

参考: https://lodash.com/docs/4.17.15#orderBy



classnames优化类名控制

classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名的显示

Untitled

现在的问题:字符串的拼接方式不够直观,也容易出错

Untitled

参考:

classnames: https://github.com/JedWatson/classnames




参考:

React:

React入门到实战导学课程_哔哩哔哩_bilibili

猜你喜欢

转载自blog.csdn.net/CaptainDrake/article/details/134510332