React 的错误边界 && Hook简介 && Swiper 与 ECharts在React中的使用 @stage3---week5--day2

错误边界

Hook 简介

swiper 与 EChart 在 React 中的使用




错误边界

过去,组件内的 JavaScript 错误会导致 React 的内部状态被破坏,并且在下一次渲染时 产生 可能无法追踪的 错误。这些错误基本上是由较早的其他代码(非 React 组件代码)错误引起的,但 React 并没有提供一种在组件中优雅处理这些错误的方式,也无法从错误中恢复。

错误边界(Error Boundaries)

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树

错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

注意

  • 错误边界无法捕获以下场景中产生的错误:
    • 事件处理(了解更多)
    • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
    • 服务端渲染
    • 它自身抛出来的错误(并非它的子组件)

如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

官网案例

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

然后你可以将它作为一个常规组件去使用:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

错误边界的工作方式类似于 JavaScript 的 catch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为成错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。
注意错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。
如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。

Hook 简介

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

useState 的简单使用

import React, { useState } from "react";

export default function Count(props) {
  let [count, setCount] = useState(0);
  // * count 就是定义的状态
  // * setCount 就是修改count方法,setCount可以更新视图

  return (
    <div>
      <button
        onClick={() => {
          setCount(count + 2);
        }}
      >
        plus +{" "}
      </button>
      <p>count:{count}</p>
      {/* <p>name:{ name }</p> */}
    </div>
  );
}

通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。
useState 唯一的参数就是初始 state。在上面的例子中,我们的计数器是从零开始的,所以初始 state 就是 0。值得注意的是,不同于 this.state,这里的 state 不一定要是一个对象 —— 如果你有需要,它也可以是。这个初始 state 参数只有在第一次渲染时会被用到。
声明多个 state 变量
你可以在一个组件中多次使用 State Hook:

function ExampleWithManyStates() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState("banana");
  const [todos, setTodos] = useState([{ text: "Learn Hooks" }]);
  // ...
}

数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字。当然,这些名字并不是 useState API 的一部分。React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的。

⚡️ useEffect Hook 的简单使用

你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。
它跟 class 组件中的 componentDidMount**、componentDidUpdate** 和 **componentWillUnmount** 具有相同的用途,只不过被合并成了一个 API。

// Count.jsx
import React, { useState, useEffect } from "react";

export default function Count(props) {
  let [count, setCount] = useState(0);
  // * count 就是定义的状态
  // * setCount 就是修改count方法,setCount可以更新视图

  useEffect(() => {
    //*  这个api就相当于å是componentDidMount 和componentDidUpdate componentWillReceiveProps

    console.log("useEffect");
    //* 数据请求
    fetch("/mock/index.json")
      .then(data => data.json())
      .then(res => {
        console.log("兵哥: Count -> res", res);
        // changeCount( count = res.count )
      })
      .catch(err => console.log(err));

    //? DOM操作
    document.querySelector("p").style.background = "red";

    //? 一些修改头部标题的操作
    document.title = count;
  });

  return (
    <div>
      <button
        onClick={() => {
          setCount(count + 2);
        }}
      >
        plus +{" "}
      </button>
      <p>count:{count}</p>
      {/* <p>name:{ name }</p> */}
    </div>
  );
}
App.js;
import React, { useState } from "react";
import "./App.css";
import Count from "./pages/Count";

function App() {
  let [flag, setFlag] = useState(true);
  let [name, setName] = useState("张三");

  return (
    <div className="App">
      <h3> React Hooks </h3>
      <button
        onClick={() => {
          setFlag((flag = !flag));
        }}
      >
        changeFlag
      </button>
      <button
        onClick={() => {
          setName((name = "王五"));
        }}
      >
        changeName
      </button>
      {flag && <Count name={name}></Count>}
    </div>
  );
}

export default App;
  • 当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数
  • 由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。(我们会在使用 Effect Hook 中跟 class 组件的生命周期方法做更详细的对比。)

    副作用函数还可以通过返回一个函数来指定如何“清除”副作用。
    例如,在下面的组件中使用副作用函数来订阅好友的在线状态,并通过取消订阅来进行清除操作:

useEffect(() => {
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

  return () => {
    //返回一个函数来指定如何“清除”副作用
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

跟 useState 一样,你可以在组件中多次使用 useEffect

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。

useContext

案例

  1. 用 React.createContext() 创建上下文
//context > index.js
import { createContext } from "react";

export const moneyContext = createContext();
  1. 目的地组件 引入创建的上下文,并通过<moneyContext.Provider value= {money}> value 进行传参
//GrandFather.jsx
import React, { useState } from "react";
import { moneyContext } from "../context";
import Father from "./Father";

function GrandFather() {
  let [money] = useState(1000);
  return (
    <div>
      <moneyContext.Provider value={money}>
        <Father></Father>
      </moneyContext.Provider>
    </div>
  );
}

export default GrandFather;
  1. 中间组件引入目的地组件
//Father.jsx
import React from "react";
import Son from "./Son";

function Father() {
  return (
    <div>
      <Son></Son>
    </div>
  );
}

export default Father;
  1. 目的地组件 引入创建的 context 上下文, 并通过 const value = useContext(moneyContext) 进行接参,就可以在函数组件中使用改值了
//Son.jsx
import React, { useContext } from "react";
import { moneyContext } from "../context";

function Son() {
  const value = useContext(moneyContext);
  return <div>{value}</div>;
}

export default Son;


✌️ Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)

那么,什么是 Hook?

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。
Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
(我们不推荐把你已有的组件全部重写,但是你可以在新组件里开始使用 Hook。)

Hook 的特点、

  • 没有破坏性改动
  • 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。
  • 100% 向后兼容的。 Hook 不包含任何破坏性改动。
  • 现在可用。 Hook 已发布于 v16.8.0。

swiper 与 EChart 在 React 中的使用

swiper 在 React 中的使用

banner 的 静态数据请求 案例

//Banner.jsx
import React, { Component, Fragment } from "react";
//1. 导入 下载的Swiper 插件 和 Swiper 的css 文件
import Swiper from "swiper";
import "swiper/css/swiper.css";

export default class Banner extends Component {
  render() {
    return (
      //2. 将Swiper 的HTML文档部分 写在 返回值中
      <Fragment>
        <div
          class="swiper-container"
          style={{ width: "100%", height: "300px" }}
        >
          <div class="swiper-wrapper">
            <div class="swiper-slide">
              <img
                style={{
                  width: "100%",
                  height: "100%"
                }}
                src="https://pic12.secooimg.com/res/topic/d48770dacbb5486c918a6cd726f30a2e.jpeg_!!0x0.webp"
              />
            </div>
            <div class="swiper-slide">
              <img
                style={{
                  width: "100%",
                  height: "100%"
                }}
                src="https://pic12.secooimg.com/res/topic/4a4598f9655d42fcb1c42430f9cc5716.jpg_!!0x0.webp"
              />
            </div>
            <div class="swiper-slide">
              <img
                style={{
                  width: "100%",
                  height: "100%"
                }}
                src="https://pic12.secooimg.com/res/topic/36c54a969b104847a1c94974f21d255f.jpg_!!0x0.webp"
              />
            </div>
          </div>
          {/* <!-- 如果需要分页器 --> */}
          <div class="swiper-pagination"></div>

          {/* <!-- 如果需要导航按钮 --> */}
          <div class="swiper-button-prev"></div>
          <div class="swiper-button-next"></div>

          {/* <!-- 如果需要滚动条 --> */}
          <div class="swiper-scrollbar"></div>
        </div>
        {/* 导航等组件可以放在container之外 */}
      </Fragment>
    );
  }

  componentDidMount() {
    // 3. 将Swiper 的实例化 写在 componentDidMount 生命周期的钩子中
    // 注意 如有多个Swiper实例话 则需要改名(保证一个实例一个类名或id)
    var mySwiper = new Swiper(".swiper-container", {
      direction: "horizontal", // 垂直切换选项
      loop: true, // 循环模式选项

      // 如果需要分页器
      pagination: {
        el: ".swiper-pagination"
      },

      // 如果需要前进后退按钮
      navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev"
      },

      // 如果需要滚动条
      scrollbar: {
        el: ".swiper-scrollbar"
      }
    });
  }
}

banner 的动态数据请求 案例

// ! Swier 动态数据的 渲染
import React, { Component, Fragment } from "react";
//* 1. 引入 swiper 及其样式
import Swiper from "swiper";
import "swiper/css/swiper.css";

export default class BannerFetch extends Component {
  constructor(props) {
    super(props);

    this.state = {
      banners: null
    };
  }
  renderBannerItem = () => {
    const { banners } = this.state;
    return (
      banners &&
      banners.map(elm => (
        <div className="swiper-slide" key={elm.id}>
          <img
            src={elm.img}
            alt="error"
            style={{ width: "100%", height: "100%" }}
          />
        </div>
      ))
    );
  };

  render() {
    return (
      //* 2.写入HTML 文档
      <Fragment>
        <div
          className="swiper-container"
          id="swiper_fetch"
          style={{ width: "100%", height: "300px" }}
        >
          <div className="swiper-wrapper">
            {/* // * 4.渲染banner 列表数据  */}
            {this.renderBannerItem()}
          </div>
          {/* <!-- 如果需要分页器 --> */}
          <div className="swiper-pagination"></div>

          {/* <!-- 如果需要导航按钮 --> */}
          <div className="swiper-button-prev"></div>
          <div className="swiper-button-next"></div>

          {/* <!-- 如果需要滚动条 --> */}
          <div className="swiper-scrollbar"></div>
        </div>
        {/* 导航等组件可以放在container之外 */}
      </Fragment>
    );
  }
  componentDidMount() {
    //* 3. 进行数据请求,将获取到的数据赋值给状态
    fetch("/mock/banner.json", { method: "get" })
      .then(response => {
        return response.json();
      })
      .then(res => {
        this.setState({
          banners: res
        });

        //* 3. 对Swiper 进行实例化,但是在componentDidUpdate()中 每次更新视图将 重复实例化,所以在setTimeout  中进行实例化
        setTimeout(() => {
          /* 
                ! 好处
                    * 1. 只执行一次,不会有重复实例化问题
                    * 2. 它是异步的宏任务,确保真实DOM渲染完毕
                */
          new Swiper("#swiper_fetch", {
            direction: "horizontal", // 垂直切换选项
            loop: true, // 循环模式选项

            // 如果需要分页器
            pagination: {
              el: ".swiper-pagination"
            },

            // 如果需要前进后退按钮
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev"
            },

            // 如果需要滚动条
            scrollbar: {
              el: ".swiper-scrollbar"
            }
          });
        }, 0);
      });
  }
  componentDidUpdate(prevProps, prevState) {
    //! 若将Swiper 的实例化 写在componentDidMount 中 每次更新视图将 重复实例化
  }
}
[
  {
    "id": 1,
    "img": "https://pic12.secooimg.com/res/topic/d48770dacbb5486c918a6cd726f30a2e.jpeg_!!0x0.webp"
  },
  {
    "id": 2,
    "img": "https://pic12.secooimg.com/res/topic/4a4598f9655d42fcb1c42430f9cc5716.jpg_!!0x0.webp"
  },
  {
    "id": 3,
    "img": "https://pic12.secooimg.com/res/topic/36c54a969b104847a1c94974f21d255f.jpg_!!0x0.webp"
  }
]

ECharts 的静态数据请求 案例

import React, { Component } from "react";
//* 1. 引入echarts 插件
import echarts from "echarts";

export default class ECharts extends Component {
  render() {
    return (
      //*  2.为 ECharts 准备一个具备大小(宽高)的 DOM
      <div>
        {/* <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM --> */}
        <div id="main" style={{ width: "100%", height: "300px" }}></div>
      </div>
    );
  }
  componentDidMount() {
    //* 3. 将E charts进行初始化即可
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById("main"));

    // 指定图表的配置项和数据
    var option = {
      title: {
        text: "某站点用户访问来源",
        subtext: "纯属虚构",
        x: "center"
      },
      tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b} : {c} ({d}%)"
      },
      legend: {
        orient: "vertical",
        left: "left",
        data: ["直接访问", "邮件营销", "联盟广告", "视频广告", "搜索引擎"]
      },
      series: [
        {
          name: "访问来源",
          type: "pie",
          radius: "55%",
          center: ["50%", "60%"],
          data: [
            { value: 335, name: "直接访问" },
            { value: 310, name: "邮件营销" },
            { value: 234, name: "联盟广告" },
            { value: 135, name: "视频广告" },
            { value: 1548, name: "搜索引擎" }
          ],
          itemStyle: {
            emphasis: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)"
            }
          }
        }
      ]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
  }
}

ECharts 的动态数据请求 案例

import React, { Component } from "react";
//* 1. 引入echarts 插件
import echarts from "echarts";

export default class ECharts extends Component {
  constructor(props) {
    super(props);

    this.state = {
      legend_data: [],
      series_data: []
    };
  }

  render() {
    return (
      //*  2.为 ECharts 准备一个具备大小(宽高)的 DOM
      <div>
        {/* <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM --> */}
        <div id="main" style={{ width: "100%", height: "300px" }}></div>
      </div>
    );
  }
  componentDidMount() {
    let { legend_data, series_data } = this.state;
    //* 3. 进行数据请求,将请求到的数据进行处理赋值,
    fetch("/mock/echarts.json", { method: "get" })
      .then(response => {
        return response.json();
      })
      .then(res => {
        // console.log( "张浩雨: ECharts -> componentDidMount -> res", res );
        res.data.forEach(elm => {
          legend_data.push(elm.name);
        });
        // console.log("张浩雨: ECharts -> componentDidMount -> legend_data", legend_data)
        legend_data = res.data;
        series_data = res.data;

        setTimeout(() => {
          // ?  4. 进行异步 初始化
          // 基于准备好的dom,初始化echarts实例
          var myChart = echarts.init(document.getElementById("main"));

          // 指定图表的配置项和数据
          var option = {
            title: {
              text: "某站点用户访问来源",
              subtext: "纯属虚构",
              x: "center"
            },
            tooltip: {
              trigger: "item",
              formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            legend: {
              orient: "vertical",
              left: "left",
              data: legend_data
            },
            series: [
              {
                name: "访问来源",
                type: "pie",
                radius: "55%",
                center: ["50%", "60%"],
                data: legend_data,
                itemStyle: {
                  emphasis: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: "rgba(0, 0, 0, 0.5)"
                  }
                }
              }
            ]
          };

          // 使用刚指定的配置项和数据显示图表。
          myChart.setOption(option);
        }, 0);
      });
  }
  componentDidUpdate(prevProps, prevState) {}
}
//publish> mock > echarts.json
{
  "data": [
    { "value": 335, "name": "直接访问" },
    { "value": 310, "name": "邮件营销" },
    { "value": 234, "name": "联盟广告" },
    { "value": 1305, "name": "视频广告" },
    { "value": 1548, "name": "搜索引擎" }
  ]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值