React全家桶 - 【React】 - 【3】组件进阶(组件通信、父子通信、兄弟通信、跨层级通信)

前言

组件通信概念

  • 组件之间 的 数据传递 ,根据组件嵌套关系的不同,有不同的通信方法(如下图所示);

一、父子通信

1.1 父传子 - props

  • 将 父组件 App 中的 name数据 传递给 他的子组件 Son;

1.1.1 实现步骤

  • 父组件 传递 数据
    • 子组件标签绑定属性
    • 属性名自定义;
  • 子组件 接收 数据
    • 子组件内部 通过 props参数 接收数据
    • 子组件函数的形参位置接收参数;
    • props 是个对象 ,里面包含了 父组件 传递的 所有数据
    • 要使用传递的数据,可以通过 点语法 或者 解构 使用;

1.1.2 props说明

  • props 可传递 任意类型数据
    • 数字、字符串、布尔值、数组、对象、函数、JSX;
  • props只读对象
    • 子组件 只能读取 props 中的数据,不能直接修改;
    • 父组件 的 数据 只能由 父组件 修改

1.1.3 父传子 - 特殊的 props children

  • 场景
    • 当我们把 内容嵌套子组件标签中时 ,父组件会自动在名为 childrenprop属性中 接收该内容

1.2 子传父

  • 点击 send按钮 的时候,将 Son组件 的 msg数据 传递给 父组件 App;
  • 实现思路
    • 子组件调用 父组件中的函数传递参数
  • 代码展示:
  • 注意
    • 如果传递给子组件的是函数,一般都是以 on 开头的;(不以这个开头也没关系,看个人习惯);

二、使用状态提升实现兄弟组件通信

  • 点击 A组件 的 send 按钮的时候,要将 A组件 的 name数据 发送给它的 兄弟组件 B;
  • 实现思路
    • 借助 "状态提升" 机制,通过 共同的父组件 进行 兄弟组件之间数据传递
  • 状态提升
    • 将需要使用的状态提升到他们共同的父组件中;
  • 代码展示:

    jsx 复制代码
    import { useState } from 'react';
    
    const App = () => {
      const [brotherDate, setBrotherDate] = useState('');
      return (
        <div>
          <div>
            父组件接收的数据(子组件触发条件之后才能传递过来):
            <span style={{ color: 'red', fontSize: '20px' }}>{brotherDate}</span>
          </div>
          <br />
          <A bDate={brotherDate} onUpdateBrotherDate={(val) => setBrotherDate(val)} />
          <br />
          <B aDate={brotherDate} onUpdateBrotherDate={(val) => setBrotherDate(val)} />
        </div>
      );
    };
    
    const A = ({ bDate, onUpdateBrotherDate }) => {
      const [val, setVal] = useState('this is A name');
      return (
        <div>
          <div>A组件: A向B传递的数据: {val}</div>
          <div>
            A组件: B传递的数据:
            <span style={{ color: 'red', fontSize: '20px' }}>{bDate}</span>
          </div>
          <input type="text" value={val} onChange={(e) => setVal(e.target.value)} />
          <button onClick={() => onUpdateBrotherDate(val)}>A组件数据 ➡ B组件</button>
        </div>
      );
    };
    
    const B = ({ aDate, onUpdateBrotherDate }) => {
      const [val, setVal] = useState('this is B name');
      return (
        <div>
          <div>
            B组件: A传递过来的数据:
            <span style={{ color: 'red', fontSize: '20px' }}>{aDate}</span>
          </div>
          <div>B组件: B向A传递的数据: {val}</div>
          <input type="text" value={val} onChange={(e) => setVal(e.target.value)} />
          <button onClick={() => onUpdateBrotherDate(val)}>B组件数据 ➡ A组件</button>
        </div>
      );
    };
    
    export default App;
  • 效果展示:

三、使用Context机制跨层级组件通信

  • 需要将 App组件 中的数据,传递给 B组件,他们之间是隔着一层A组件的,使用 context 机制进行数据的传递;
  • 实现步骤
    • 使用 createContext 方法创建一个 上下文对象 Ctx(上下文对象的名字可以随便起);
    • 顶层组件 (App)中通过 Ctx.Provider组件 (高阶组件) 提供数据
    • 底层组件 (B)中通过 useContext 钩子函数 使用数据
  • 注意
    • Ctx.Provider 组件上有个 value属性,用来提供数据,属性名只能是value
    • 一定要使用 Ctx.Provider 组件 包裹住 层级组件 ,否则在底层组件使用 useContext 钩子得到的数据就是 undefined
  • 代码展示:

    jsx 复制代码
    import { useContext } from 'react';
    import { createContext } from 'react';
    
    // 1、使用 createContext 创建上下文对象
    const MsgContext = createContext();
    
    const A = () => {
      return (
        <div>
          <h2>this is A Component</h2>
          <B />
        </div>
      );
    };
    
    const B = () => {
      // 3、在底层组件,使用 useContext 钩子函数获取数据
      const msg = useContext(MsgContext);
      return (
        <div>
          <h3>this is B Component</h3>
          {msg}
        </div>
      );
    };
    
    const App = () => {
      const msg = 'this is app msg';
      // 2、在顶层组件,通过 Provider将数据传递给底层组件
      return (
        <div>
          {/* value属性提供数据,不能更改属性名,只能使用value */}
          {/* 一定要使用 MsgContext.provider 组件包裹住层级组件 */}
          <MsgContext.Provider value={msg}>
            <h1>this is App Component</h1>
            <A />
          </MsgContext.Provider>
        </div>
      );
    };
    
    export default App;
  • 演示效果:

四、案例展示 - B站评论 - 组件版

  • css文件请移步至上篇文章的末尾自取;
  • 效果展示:
  • 功能需求:
    • 渲染评论列表;
    • 实现删除评论;
      • 只有自己的评论才显示删除按钮;
      • 点击删除按钮,删除当前评论,列表中不再显示;
    • 渲染导航Tab和高亮实现;
    • 评论列表排序功能实现;
      • 最新:评论列表按照创建时间排序(新的在前);
      • 最热:点赞数排序(点赞数多的在前);
    • 实现评论功能;
      • 点击发布之后,需要清空输入框的内容,并且自动获取焦点;
      • 回车也可以发送评论;
  • 核心思路:
    • 使用 useState 维护评论列表;
    • 使用 map 对列表进行遍历渲染(一定要添加key);
    • 将代码拆分成组件形式;
    • 使用组件通信;
  • 代码展示:

    jsx 复制代码
    import { createContext, useContext, useRef, useState } from 'react';
    import _ from 'lodash';
    import './bilibili-review.scss';
    import avatar from './images/bozai.png';
    
    // 评论列表数据
    const defaultList = [
      {
        // 评论id
        rpid: 3,
        // 用户信息
        user: {
          uid: '13258165',
          avatar: 'https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/reactbase/comment/zhoujielun.jpeg',
          uname: '周杰伦'
        },
        // 评论内容
        content: '哎哟,不错哦',
        // 评论时间
        ctime: '2023-10-18 08:15',
        // 喜欢数量
        like: 98,
        // 0:未表态 1: 喜欢 2: 不喜欢
        action: 0
      },
      {
        rpid: 2,
        user: {
          uid: '36080105',
          avatar: 'https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/reactbase/comment/xusong.jpeg',
          uname: '许嵩'
        },
        content: '我寻你千百度 日出到迟暮',
        ctime: '2023-11-13 11:29',
        like: 88,
        action: 2
      },
      {
        rpid: 1,
        user: {
          uid: '30009257',
          avatar,
          uname: '黑马前端'
        },
        content: '学前端就来黑马',
        ctime: '2023-10-19 09:00',
        like: 66,
        action: 1
      }
    ];
    
    // 当前登录用户信息
    const user = {
      // 用户id
      uid: '30009257',
      // 用户头像
      avatar,
      // 用户昵称
      uname: '黑马前端'
    };
    
    const MsgContext = createContext();
    
    // 头部导航栏
    const HeaderTab = ({ list, updateList }) => {
      const tabOptions = [
        { type: 'latest', text: '最新' },
        { type: 'hottest', text: '最热' }
      ];
      let [activeTab, setActiveTab] = useState('latest');
      /** 切换Tab栏 */
      const updateActiveTab = (type) => {
        setActiveTab(type);
        updateList(type);
      };
    
      return (
        <div className="header align-center">
          <div className="title align-center">
            评论<em>{list.length}</em>
          </div>
          <ul className="tab align-center">
            {tabOptions.map((item, index) => {
              return (
                <li
                  className={`item align-center ${activeTab === item.type && 'active'}`}
                  key={item.type}
                  onClick={() => updateActiveTab(item.type)}>
                  <span>{item.text}</span>
                  {tabOptions.length - 1 !== index && <span className="split-line"></span>}
                </li>
              );
            })}
            {/*
              // 多类名写法
              // className={['item', 'align-center', activeTab === item.type ? 'active' : null].join(' ')}
              // className={['item', 'align-center', activeTab === item.type && 'active'].join(' ')}
              // className={`item align-center ${activeTab === item.type ? 'active' : null}`}
              // className={`item align-center ${activeTab === item.type && 'active'}`}
            */}
          </ul>
        </div>
      );
    };
    
    // 评论输入框
    const PostReview = ({ updateList }) => {
      // 受控表单绑定
      const [content, setContent] = useState('');
      // 输入框ref
      const inputRef = useRef(null);
      /** 发布评论 */
      const addReviewItem = (e) => {
        if (e.key === 'Enter') e.preventDefault();
        if (!content) return alert('请输入评论!');
        const time = new Date().toLocaleString();
        const m_d = time.split(' ')[0].split('/');
        const h_m = time.split(' ')[1].split(':');
        h_m.pop();
        const obj = {
          rpid: new Date().getTime(),
          user,
          content,
          ctime: `${m_d.join('-')} ${h_m.join('-')}`,
          like: 0,
          action: 0
        };
        updateList('add', '', obj);
        setContent('');
        // 自动获取焦点
        inputRef.current.focus();
      };
    
      return (
        <div className="post-review align-center">
          <img src={user.avatar} alt={user.uname} className="avatar" />
          <div className="input align-center">
            <textarea
              type="text"
              ref={inputRef}
              value={content}
              onChange={(e) => setContent(e.target.value)}
              onKeyDown={(e) => e.key === 'Enter' && addReviewItem(e)}
              placeholder="发一条友善的评论"
            />
            <button onClick={addReviewItem}>发布</button>
          </div>
        </div>
      );
    };
    
    // 一条评论
    const ReviewItem = ({ item }) => {
      const updateList = useContext(MsgContext);
    
      return (
        <div className="review-item">
          <div className="left">
            <img src={item.user.avatar} alt={item.user.uname} />
          </div>
          <div className="right">
            <div className="user-name">{item.user.uname}</div>
            <div className="content">{item.content}</div>
            <div className="bottom">
              <div className="time">{item.ctime}</div>
              <div className="like-num">点赞数:{item.like}</div>
              <ul className="controls">
                {item.user.uid === user.uid && (
                  <li className="del" onClick={() => updateList('del', item.rpid)}>
                    删除
                  </li>
                )}
              </ul>
            </div>
          </div>
        </div>
      );
    };
    
    // 评论列表
    const ReviewList = ({ list }) => {
      return (
        <div className="review-list">
          {list.map((item) => {
            return <ReviewItem item={item} key={item.rpid} />;
          })}
        </div>
      );
    };
    
    const App = () => {
      // 评论数据
      const [list, setList] = useState(_.orderBy(defaultList, 'ctime', 'desc'));
    
      /** 更新评论列表:排序 + 删除 + 新增 */
      const updateList = (type, id, newReview = {}) => {
        let newList = [];
        switch (type) {
          case 'latest':
            newList = _.orderBy(list, 'ctime', 'desc');
            break;
          case 'hottest':
            newList = _.orderBy(list, 'like', 'desc');
            break;
          case 'del':
            newList = list.filter((item) => item.rpid !== id);
            break;
          case 'add':
            newList = [newReview, ...list];
            break;
          default:
            newList = list;
            break;
        }
        setList(newList);
      };
    
      return (
        <div className="app">
          <HeaderTab list={list} updateList={updateList} />
          <div className="review-box">
            <PostReview updateList={updateList} />
            {/* 使用 context 机制实现跨层通信 */}
            <MsgContext.Provider value={updateList}>
              <ReviewList list={list} />
            </MsgContext.Provider>
          </div>
        </div>
      );
    };
    
    export default App;
相关推荐
hahaqi9527几秒前
layui 表格点击编辑感觉很好用,实现方法如下
前端·javascript·layui
我爱学习_zwj12 分钟前
ArkTS的进阶语法-4(函数补充,正则表达式)
前端·华为·正则表达式·harmonyos
北【辰】、12 分钟前
uview Collapse折叠面板无法动态设置展开问题(微信小程序)
javascript·vue.js·微信小程序·小程序·前端框架
cdcdhj42 分钟前
利用服务工作线程serviceWorker缓存静态文件css,html,js,图片等的方法,以及更新和删除及版本控制
javascript·css·缓存·html
咔咔库奇1 小时前
【CSS问题】margin塌陷
前端·javascript·css
无敌最俊朗@1 小时前
c#————委托Action使用例子
java·前端·c#
见过夏天1 小时前
CSS 中渐变色的使用
前端·css
764331 小时前
JavaScript ES6 继承 class
前端·javascript
袁代码1 小时前
SwiftUI开发教程系列 - 第十二章:本地化与多语言支持
开发语言·前端·ios·swiftui·swift·ios开发
软件聚导航2 小时前
在uniapp中使用canvas封装组件遇到的坑,数据被后面设备覆盖,导致数据和前面的设备一样
java·前端·uni-app