Bootstrap

React基础语法

1.React介绍

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

1.1 React优势

相较于传统基于DOM开发的优势

1.组件化的开发方式

2.不错的性能

相较于其他前端框架的优势

1.丰富的生态

2.跨平台支持

1.2React的时长情况

全球最流行,大厂必备

2.开发环境创建

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

npx create-react-app react-basic

cd react-basic
npm start

创建项目更多方式:

https://zh-hans.react.dev/learn/start-a-new-react-project

3.JSX基础

3.1什么是JSX

JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模板结构,是React构建UI的方式

const message = 'this is message'

function App() {
  return (
    <div className="App">
      <h1>My First React App</h1>
      {message}
    </div>
  );
}

export default App;

3.2 JSX的本质

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

3.3JSX高频场景-JS表达式

在JSX中可以通过 <font style="color:rgb(119, 119, 119);background-color:rgb(248, 248, 248);">大括号语法{}</font> 识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

使用引号传递字符串、JS变量、函数调用和方法调用、对象

注意:不能使用条件语句

const message = 'this is message'

function getAge(){
  return 18
}

function App() {
    return (
        <div className="App">
            <h1>My First React App</h1>
            {message}
          {/*函数调用*/}
            {getAge()}
        </div>
    );
}

export default App;

3.4JS高频场景-列表场景

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

function App(){
    return (
        <ul>
            {list.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
    );
}

export default App;


3.5JS高频场景-条件渲染

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

const flag = true;
const loading = true;

function App() {
    return (
        <>
            {flag && <span>this is span</span>}
            {loading ? <span>loading...</span>:<span>this is span</span>}
        </>
    )
}

export default App;

3.6JSX高频场景-复杂条件渲染

需求:列表中需要根据文章的状态适配

const type = 0; // 0 1 3


function getArticleJSX() {
    if (type === 0) {
        return <div>无图模式</div>
    } else if (type === 1) {
        return <div>单图模式</div>
    } else if(type === 3)
    {
        return <div>多图模式</div>
    }
}


function App() {
    return (
        <>
            {getArticleJSX()}
        </>
    )
}

export default App;

4.React的事件绑定

4.1基础实现

React中的事件绑定,通过语法on + 事件名称 = {事件处理程序}。整体上遵循驼峰命名

function App() {
  const clickHandle = () =>{
    console.log('button按钮点击了');
  }
    return (
       <button onClick={clickHandle}>click me</button>
    )
}

export default App;

4.2使用事件参数

在事件回调函数中设置形参e即可

function App() {
  const clickHandle = (e) =>{
    console.log('button按钮点击了', e);
  }
    return (
       <button onClick={clickHandle}>click me</button>
    )
}

export default App;

4.3传递自定义参数

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

function App() {
    const clickHandle = (name) =>{
        console.log('button按钮点击了', name);
    }
    return (
        <button onClick={()=>clickHandle('jack')}>click me</button>
    )
}

export default App;

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

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

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

function App() {
    const clickHandle = (name, e) =>{
        console.log('button按钮点击了', name, e);
    }
    return (
        <button onClick={(e)=>clickHandle('jack', e)}>click me</button>
    )
}

export default App;

5.React组件基础使用

5.1 组件是什么

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

5.2组件基础使用

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

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

export default App;

6.组件状态管理-useStatue

6.1基础使用

useState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果和普通的JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着发生变化(数据驱动试图)

import React from "react";

function App() {
    const [count, setCount] = React.useState(0)
    return (
        <div>
            <button onClick={()=>setCount(count + 1)}>{count}</button>
        </div>
    )
}

export default App;

6.2状态的修改规则

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

6.3修改对象状态

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

import React from "react";

function App() {
    const [form, setForm] = React.useState({
        name : 'jack'
    })
    const handleChageName=() => {
        setForm({
            ...form,
            name : 'john'
        })
    }
    return (
        <div>
            <button onClick={()=>handleChageName()}>{form.name}</button>
        </div>
    )
}

export default App;

7.组建的基础样式处理

React组件基础的样式控制有两种方式,行内样式和class类名控制

import React from "react";


function App() {

    return (
        <div>
           <div style={{color:'red'}}> this is div</div>
        </div>
    )
}

export default App;

index.css

.foo{
  color: red;
}

App.js

import './index.css';


function App() {

    return (
        <div>
           <div className="foo"> this is div</div>
        </div>
    )
}

export default App;

8.B站评论案例

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

8.1基础项目

App.js

import './App.scss'
import avatar from './images/bozai.png'

/**
 * 评论列表的渲染和操作
 *
 * 1. 根据状态渲染评论列表
 * 2. 删除评论
 */

// 评论列表数据
const defaultList = [
  {
    // 评论id
    rpid: 3,
    // 用户信息
    user: {
      uid: '13258165',
      avatar: '',
      uname: '周杰伦',
    },
    // 评论内容
    content: '哎哟,不错哦',
    // 评论时间
    ctime: '10-18 08:15',
    like: 88,
  },
  {
    rpid: 2,
    user: {
      uid: '36080105',
      avatar: '',
      uname: '许嵩',
    },
    content: '我寻你千百度 日出到迟暮',
    ctime: '11-13 11:29',
    like: 88,
  },
  {
    rpid: 1,
    user: {
      uid: '30009257',
      avatar,
      uname: '黑马前端',
    },
    content: '学前端就来黑马',
    ctime: '10-19 09:00',
    like: 66,
  },
]
// 当前登录用户信息
const user = {
  // 用户id
  uid: '30009257',
  // 用户头像
  avatar,
  // 用户昵称
  uname: '黑马前端',
}

/**
 * 导航 Tab 的渲染和操作
 *
 * 1. 渲染导航 Tab 和高亮
 * 2. 评论列表排序
 *  最热 => 喜欢数量降序
 *  最新 => 创建时间降序
 */

// 导航 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">
            {/* 高亮类名: active */}
            <span className='nav-item'>最新</span>
            <span className='nav-item'>最热</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 className="send-text">发布</div>
            </div>
          </div>
        </div>
        {/* 评论列表 */}
        <div className="reply-list">
          {/* 评论项 */}
          <div className="reply-item">
            {/* 头像 */}
            <div className="root-reply-avatar">
              <div className="bili-avatar">
                <img
                  className="bili-avatar-img"
                  alt=""
                />
              </div>
            </div>

            <div className="content-wrap">
              {/* 用户名 */}
              <div className="user-info">
                <div className="user-name">jack</div>
              </div>
              {/* 评论内容 */}
              <div className="root-reply">
                <span className="reply-content">这是一条评论回复</span>
                <div className="reply-info">
                  {/* 评论时间 */}
                  <span className="reply-time">{'2023-11-11'}</span>
                  {/* 评论数量 */}
                  <span className="reply-time">点赞数:{100}</span>
                  <span className="delete-btn">
                    删除
                  </span>

                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default App

App.scss

.app {
  width: 80%;
  margin: 50px auto;
}

.reply-navigation {
  margin-bottom: 22px;

  .nav-bar {
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0;
    list-style: none;

    .nav-title {
      display: flex;
      align-items: center;
      width: 114px;
      font-size: 20px;

      .nav-title-text {
        color: #18191c;
        font-weight: 500;
      }
      .total-reply {
        margin: 0 36px 0 6px;
        color: #9499a0;
        font-weight: normal;
        font-size: 13px;
      }
    }

    .nav-sort {
      display: flex;
      align-items: center;
      color: #9499a0;
      font-size: 13px;

      .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: #18191c;
      }
    }
  }
}

.reply-wrap {
  position: relative;
}
.box-normal {
  display: flex;
  transition: 0.2s;

  .reply-box-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 80px;
    height: 50px;
  }

  .reply-box-wrap {
    display: flex;
    position: relative;
    flex: 1;

    .reply-box-textarea {
      width: 100%;
      height: 50px;
      padding: 5px 10px;
      box-sizing: border-box;
      color: #181931;
      font-family: inherit;
      line-height: 38px;
      background-color: #f1f2f3;
      border: 1px solid #f1f2f3;
      border-radius: 6px;
      outline: none;
      resize: none;
      transition: 0.2s;

      &::placeholder {
        color: #9499a0;
        font-size: 12px;
      }
      &:focus {
        height: 60px;
        background-color: #fff;
        border-color: #c9ccd0;
      }
    }
  }

  .reply-box-send {
    position: relative;
    display: flex;
    flex-basis: 86px;
    align-items: center;
    justify-content: center;
    margin-left: 10px;
    border-radius: 4px;
    cursor: pointer;
    transition: 0.2s;

    & .send-text {
      position: absolute;
      z-index: 1;
      color: #fff;
      font-size: 16px;
    }
    &::after {
      position: absolute;
      width: 100%;
      height: 100%;
      background-color: #00aeec;
      border-radius: 4px;
      opacity: 0.5;
      content: '';
    }
    &:hover::after {
      opacity: 1;
    }
  }
}
.bili-avatar {
  position: relative;
  display: block;
  width: 48px;
  height: 48px;
  margin: 0;
  padding: 0;
  border-radius: 50%;
}
.bili-avatar-img {
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: 48px;
  height: 48px;
  object-fit: cover;
  border: none;
  border-radius: 50%;
  image-rendering: -webkit-optimize-contrast;
  transform: translate(-50%, -50%);
}

// 评论列表
.reply-list {
  margin-top: 14px;
}
.reply-item {
  padding: 22px 0 0 80px;
  .root-reply-avatar {
    position: absolute;
    left: 0;
    display: flex;
    justify-content: center;
    width: 80px;
    cursor: pointer;
  }

  .content-wrap {
    position: relative;
    flex: 1;

    &::after {
      content: ' ';
      display: block;
      height: 1px;
      width: 100%;
      margin-top: 14px;
      background-color: #e3e5e7;
    }

    .user-info {
      display: flex;
      align-items: center;
      margin-bottom: 4px;

      .user-name {
        height: 30px;
        margin-right: 5px;
        color: #61666d;
        font-size: 13px;
        line-height: 30px;
        cursor: pointer;
      }
    }

    .root-reply {
      position: relative;
      padding: 2px 0;
      color: #181931;
      font-size: 15px;
      line-height: 24px;
      .reply-info {
        position: relative;
        display: flex;
        align-items: center;
        margin-top: 2px;
        color: #9499a0;
        font-size: 13px;

        .reply-time {
          width: 86px;
          margin-right: 20px;
        }
        .reply-like {
          display: flex;
          align-items: center;
          margin-right: 19px;

          .like-icon {
            width: 14px;
            height: 14px;
            margin-right: 5px;
            color: #9499a0;
            background-position: -153px -25px;
            &:hover {
              background-position: -218px -25px;
            }
          }
          .like-icon.liked {
            background-position: -154px -89px;
          }
        }
        .reply-dislike {
          display: flex;
          align-items: center;
          margin-right: 19px;
          .dislike-icon {
            width: 16px;
            height: 16px;
            background-position: -153px -153px;
            &:hover {
              background-position: -217px -153px;
            }
          }
          .dislike-icon.disliked {
            background-position: -154px -217px;
          }
        }
        .delete-btn {
          cursor: pointer;
          &:hover {
            color: #00aeec;
          }
        }
      }
    }
  }
}

.reply-none {
  height: 64px;
  margin-bottom: 80px;
  color: #99a2aa;
  font-size: 13px;
  line-height: 64px;
  text-align: center;
}

8.2完成版本

8.2.1列表渲染

{/* 评论项 */}
                  {
                    list.map((item) => (
                        <div key={item.user.uid} className="reply-item">
                          {/* 头像 */}
                          <div className="root-reply-avatar">
                            <div className="bili-avatar">
                              <img
                                  className="bili-avatar-img"
                                  alt=""
                              />
                            </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>
                                <span className="delete-btn" onClick={()=>deleteItem(item.rpid)}>
删除
</span>
                              </div>
                            </div>
                          </div>
                        </div>
                    ))

8.2.2 删除评论

 const [list, setList] = useState(defaultList)
    const deleteItem = (rpid) => {
        setList(list.filter(item => item.rpid !== rpid));
    }

8.3.2渲染导航Tab和高亮显示

 <li className="nav-sort">
                        {/* 高亮类名: active */}
                        {tabs.map((tab) => (
                            <span
                                key={tab.type}
                                className={`nav-item ${tab.type === activeTabType ? 'active' : ''}`}
                                onClick={() => tabChange(tab.type)}>{tab.text}</span>
                        ))}

                        {/*<span className='nav-item' onClick={()=>tabChange('time')}>最热</span>*/}
                    </li>
 const [activeTabType, setActiveTabType] = useState(tabs[0].type); // 初始激活第一个 tab

    const tabChange = (type) => {
        setActiveTabType(type);
    };

8.3.4评论列表排序

安装lodash库

npm install lodash
const [list, setList] = useState(defaultList)
useEffect(() => {
  // 根据点赞数量排序(热度排序)
  const sortedComments = _.orderBy(list, 'like', 'desc')
  setList(sortedComments);
}, []); // 空依赖数组表示这个 effect 只在组件挂载和卸载时运行一次
const deleteItem = (rpid) => {
  setList(list.filter(item => item.rpid !== rpid));
}
const [activeTabType, setActiveTabType] = useState(tabs[0].type); // 初始激活第一个 tab

const tabChange = (type) => {
  setActiveTabType(type);
  if (type === 'hot') {
    setList(_.orderBy(list, 'like', 'desc'));
  } else if (type === 'time') {
    setList(_.orderBy(list, 'ctime', 'asc'));
  }
};

8.3.5 样式书写优化

npm install classnames 
className={classNames('nav-item', {active: tab.type === activeTabType})}

9.React表单控制

9.1受控绑定

概念:使用React组件的状态(useState)控制表单状态

import {useState} from "react";


function App() {
  const [value, setValue] = useState('');
  return (
    <input type="text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      >

    </input>
  );
}

export default App;

9.2非受控绑定

概念:通过DOM的方式获取表单的输入数据

import {useRef} from "react";

function App() {
    var inputRef = useRef(null);
    const onChange = () => {
        console.log(inputRef.current.value)
    }
  return (
      <input type="text"
             ref={inputRef}
             onChange={onChange}>
      </input>
  );
}

export default App;

10.案例-B站评论案例

1.输入评论内容,并发布评论

1)进行表单受控绑定

<div className="reply-box-wrap">
  {/* 评论框 */}
  <textarea
    className="reply-box-textarea"
    placeholder="发一条友善的评论"
    value = {textValue}
    onChange={(e)=>setTextValue(e.target.value)}
    />
  {/* 发布按钮 */}
  <div className="reply-box-send">
    <div className="send-text" onClick={()=>sendComment(textValue)}>发布</div>
  </div>
</div>

2)点击“发布”触发发布评论

const [textValue, setTextValue] = useState('');
const sendComment = () => {
  setList([
    ...list,
    {
      // 评论id
      rpid: 3,
      // 用户信息
      user: user,
      // 评论内容
      content: textValue,
      // 评论时间
      ctime: new Date().toLocaleString(),
      like: 88,
    }
  ])
  setTextValue('');

}

3)非受控绑定输入框

<div className="reply-box-wrap">
  {/* 评论框 */}
  <textarea
    className="reply-box-textarea"
    placeholder="发一条友善的评论"
    value = {textValue}
    ref={inputRef}
    onChange={(e)=>setTextValue(e.target.value)}
    />
  {/* 发布按钮 */}
  <div className="reply-box-send">
    <div className="send-text" onClick={()=>sendComment(textValue)}>发布</div>
  </div>
</div>

4)光标聚焦

const sendComment = () => {
       setList([
           ...list,
           {
               // 评论id
               rpid: 3,
               // 用户信息
               user: user,
               // 评论内容
               content: textValue,
               // 评论时间
               ctime: new Date().toLocaleString(),
               like: 88,
           }
       ])
        setTextValue('');
       inputRef.current.focus();

    }

2.id处理和时间处理(uuidday.js

npm install dayjs --save
npm install uuid --save

具体代码实现

const sendComment = () => {
  setList([
    ...list,
    {
      // 评论id
      rpid: uuidV4(),
      // 用户信息
      user: user,
      // 评论内容
      content: textValue,
      // 评论时间
      ctime: dayjs(new Date()).format('MM-DD hh:mm'),
      like: 88,
    }
  ])
  setTextValue('');
  inputRef.current.focus();

}

11.React组件通信

概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信手段和方法

  1. A-B 父子通信
  2. B-C 兄弟通信
  3. A-E 跨层通信

11.1 父子通信-父传子

11.1.1 基础实现

实现步骤:

1.父组件传递数据-在子组件标签上绑定属性

2.子组件接收数据-子组件通过props参数接受数据

function Son(props) {
    return (<div>{props.name}</div>)
}

function App() {
    const  name = 'react'
  return (
     <div><Son name={name} /></div>
  );
}

export default App;

11.1.2props说明

props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX

props是只读对象

子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改

11.1.3特殊的prop-chilren

场景:当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接受该内容

function Son(props) {
    return <div>{props.children}</div>
}
function App() {
    const  name = 'react'
  return (
    <Son>
        <span>this is span</span>
    </Son>
  );
}

export default App;

11.2 父子通信-子传父

核心思想:子组件中调用父组件中的参数并传递函数

function Son({ onGetMsg}) {
    const msg = "this is son msg";
    return (
        <div>
            <button onClick={() => onGetMsg(msg)}>send</button>
        </div>
    )
}
function App() {
    const getMsg = (msg) => {
        console.log(msg)
    }
   return (
       <div>
           <Son onGetMsg={getMsg}></Son>
       </div>
   );
}

export default App;

11.3兄弟组件通信

实现思路:

借助状态提升机制,通过共同的父组件进行相抵之间的数据传递

import {useState} from "react";

function A({onGetAName}) {
  const mag = "this is A name";
  return (
    <div>
      this is A,
      <button onClick={() => onGetAName(mag)}>send</button>
    </div>
  );
}

function B({name}) {
  return <div>this is B,{name}</div>
}

function App() {
  const [name, setName] = useState("");
  const getAName = (name) => {
    setName(name);
  }

  return (
    <div>
      <A onGetAName={getAName}></A>
      <B name={name}></B>
    </div>
  );
}

export default App;

11.4 跨层组件通信

实现步骤:

1.使用createContext方法创建一个上下文对象Ctx

2.在顶层组件{App}中通过Ctx.Provider组件提供数据

3.在底层组件{B}中通过useContext钩子函数获取消费消息

import {createContext, useContext} from "react";

var MsgContext = createContext();

function A() {
    const msg = "this is A Component";
    return (
        <div>
            {msg}.
           <B/>
        </div>
    );
}

function B() {
    const msg = useContext(MsgContext);
    return <div>this is B,{msg}</div>
}

function App() {
    const msg = "this is App Component";
    return (
        <div>
            <MsgContext.Provider value={msg}>
                <A/>
            </MsgContext.Provider>
        </div>
    );
}

export default App;

12.React副作用管理-useEffect

12.1概念理解

useEffect是一个React Hook函数,用于在React组件中创建不是由时间引起而是由渲染本身引起的操作(副作用),比如发送AJAX请求,更改DOM等等

说明:上面的组件中没有发生任何的用户事件,组件渲染完毕之后就需要跟服务器要数据,整个过程属于“只渲染引起的操作”

12.2基础使用

需求:在组建渲染完毕之后,立刻从服务器端获取数据并展示在页面中

useEffect(() => { }, [])

说明:

  1. 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
  2. 参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

接口地址:http://geek.itheima.net/v1_0/channels

1)安装axios

npm install axios 

2)发送请求

const [list, setList] = useState([]);
useEffect(() => {
  const getData = async () => {
    const res = await axios.get("http://geek.itheima.net/v1_0/channels");
    console.log(res.data.data.channels)

    setList(res.data.data.channels)
  }
  getData();

}, []);

3)展示数据

import axios from "axios";
import {useEffect, useState} from "react";


function App() {
  const [list, setList] = useState([]);
  useEffect(() => {
    const getData = async () => {
      const res = await axios.get("http://geek.itheima.net/v1_0/channels");
      console.log(res.data.data.channels)

      setList(res.data.data.channels)
    }
    getData();

  }, []);

  return (
    <div>
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

12.3useEffect依赖说明

useEffect副作用函数执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现

依赖项副作用功函数的执行时机
没有依赖项组件初始渲染 + 组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 依赖项变化时执行

12.4清除副作用

概念:在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用函数,比如在useEffect中开启一个定时器,在组件卸载时把这个定时器清除掉,这个过程就是清楚副作用。

useEffect(()=>{
  return ()=>{
    // 清除副作用
  }
},[])

说明:清除副作用函数最常见的执行时机是在组件卸载时自动执行

import {useEffect, useState} from "react";

function Son() {
    useEffect(() => {
        const timer = setInterval(() => {
            console.log("我是子组件的定时器")
        }, 1000)
        return () => {
            clearInterval(timer)
            console.log("子组件的定时器被销毁了")
        }
    }, []);
    return (<div>我是子组件</div>)
}


function App() {
    const [show, setShow] = useState(true)

    return (
        <div>
            {show && <Son/>}
            <button onClick={() => setShow(false)}>卸载组件</button>
        </div>
    );
}

export default App;

13.自定义Hook

13.1自定义Hook实现

概念:自定义Hook是以use大头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

封装自定义hook通用思路:

1.声明一个以use打头的函数

2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)

3.把组件中用到的状态或者回调return出去(以对象或者数组)

4.在哪个组件中要用这个逻辑,就执行这个函数,结构出来状态和回调进行使用

import {useState} from "react";

function useToggle() {
    const [value, setValue] = useState(true);
    const toggle = () => setValue(!value);
    return {
        value, toggle
    };
}


function App() {
    const {value, toggle} = useToggle();

    return (
        <div>
            {value && <div>this is div</div>}
            <button onClick={toggle}>toggle</button>
        </div>
    );
}

export default App;

13.2React Hooks使用规则

1.只能在组件或者其他自定义Hook函数中调用

2.只能在组件的顶层调用,不能嵌套在if、for、其它函数中

14.案例-优化B站评论案例

14.1使用接口的方式获取评论列表并渲染

1)使用json-server工具模拟接口服务,通过axios发送请求

下载json-server到当前项目

npm i json-server -D

在项目文件夹下创建db.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
    }
  ]
}

在package.json下添加启动命令

"serve": "json-server db.json --port 3004"

2)使用useEffect调用接口获取数据

封装Hook调用接口函数

function useGetList() {
  const [list, setList] = useState([]);
  useEffect(() => {
    async function getList() {
      const res = await axios.get('http://localhost:3004/list');
      setList(res.data);
    }

    getList()

  }, []);

  return {
    list,
    setList
  }

}

调用Hook函数

 const {list, setList} = useGetList();

14.2把评论中的每一项抽象成一个独立的组件实现渲染

1)封装组件

function Item({item, onDel}) {
  return (
    <div key={item.rpid} className="reply-item">
      {/* 头像 */}
      <div className="root-reply-avatar">
        <div className="bili-avatar">
          <img
            className="bili-avatar-img"
            alt=""
            />
        </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>
            <span className="delete-btn" onClick={() => onDel(item.rpid)}>
              删除
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}

2)调用组件

<div className="reply-list">
  {/* 评论项 */}
  {
    list.map((item) => (
      <Item key={item.id} item={item} onDel={deleteItem}/>
    ))
  }
</div>
;