【react小项目】bmi-calculator

news2024/12/27 10:55:46

bmi-calculator

目录

  • bmi-calculator
    • 初始化项目
    • 01大致布局
      • 01代码
    • 02完善样式
      • 02代码
    • 03输入信息模块
      • 03代码
    • 04 使用图表
      • 04代码
    • 05详细记录信息渲染
      • 05代码
    • 06 让数据变成响应式的
      • 06-1输入框的数据处理
      • 06-2图表,和记录信息的区域数据处理
    • 07 删除功能,撤销功能
      • 删除功能完成
      • 撤销功能
    • 08 数据持久化、组件化、模块化
      • 08-1数据持久化
        • 存数据
        • 取数据
        • Undo 使用本地化数据,不使用useRef()缓存了
      • 08-2组件化、模块化
        • 08-2-1输入添加模块
        • 08-2-2图表模块
        • 08-2-3七天数据模块
    • 09 修一些bug

学习地址:https://gitee.com/cheng_yong_xu/bmi-calculator-my

源码地址:https://github.com/GermaVinsmoke/bmi-calculator

对于学习react的同学,这是个不错的学习项目,循序渐进, 很多注释

可以学到什么
函数组件
useState, useEffect,useRef
prop-types
materialize-css
react-chartjs-2(折线图)
数据本地存储
模块化,组件化

成品效果
在这里插入图片描述

初始化项目

第一次提交

在这里插入图片描述

App组件

在这里插入图片描述

启动

在这里插入图片描述

01大致布局

【分支01】

使用了Materialize CSS框架的网格系统(Grid System)来布局页面内容。

在这里插入图片描述

01代码

import React, { useState, useEffect } from 'react';
import 'materialize-css/dist/css/materialize.min.css';
import './App.css'
const App = () => {
  return (
    <div className='container'>
      {/* 标题 */}
      <div className='row center'>
        <h1 className='white-text'>BMI Tracker</h1>
      </div>


      {/* 输入框 */}
      <div className='row'>
        <div className='col m12 s12'>
          <div className='row'>
            <div className='col m6 s12'>
              <label htmlFor="weight">Weight (in kg)</label>
              <input
                type="number"
                id="weight"
                name="weight"
                min="1"
                max="999"
                placeholder="50"
              />
            </div>

            <div className='col m6 s12'>
              <label htmlFor="height">Height (in cm)</label>
              <input
                type="number"
                id="height"
                name="height"
                min="1"
                max="999"
                placeholder="175"
              />
            </div>
          </div>

          <div className='center'>
            <button
              id="bmi-btn"
              className="calculate-btn"
              type="button"
            >Calculate BMI</button>
          </div>
          { }
        </div>
//

      </div>

      {/* 统计图 */}
      <div className='row center white-text'>
        统计图
      </div>

      {/* 详细记录信息 */}
      <div>
        <div className='row center'>
          <h2 className='white-text'>7 Day Data</h2>
        </div>

        <div me='data-container row'>
          <div className="col m6 s12">
            <div className="card">
              <div className="card-content">
                <span className="card-title" data-test="bmi">
                  BMI: 20.1
                </span>
                <div className="card-data">
                  <span data-test="weight">Weight: 70 kg</span>
                  <span data-test="height">Height: 180 cm</span>
                  <span data-test="date">Date: 2022/12/12</span>
                </div>
                <button className="delete-btn">
                  X
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className='center'>
        <button className='calculate-btn'>Undo</button>
      </div>


    </div>
  )
}

export default App;

02完善样式

【分支02】
在这里插入图片描述

02代码

body{
  background-color: #172B4D;
}
/* .center h1 {
  color: #fff;
} */

input {
  background-color: #fff !important;
  border-radius: 44px !important;
  width: 90% !important;
  padding: 0px 15px !important;
}

input:focus {
  border-bottom: none !important;
  box-shadow: none !important;
}

label {
  display: block;
  color: #fff !important;
  font-size: 1rem !important;
}

.calculate-btn{
  background-color: #3f51b5;
  padding: 15px 50px;
  color: white;
  font-size: 16px;
  border-radius: 44px;
  cursor: pointer;
  border: 1px solid #3f51b5;
  margin-bottom: 40px;
  transform: translate3d(0, 0, 0);
  transition: all 0.2s ease;
}

.calculate-btn:hover {
  background-color: #fff;
  transform: translate(0px, -2px);
  color: #5364c3;
  box-shadow: 0px 15px 30px -12px rgba(255, 255, 255, 0.2);
}

.calculate-btn:focus {
  background-color: #32408f;
}

.calculate-btn:focus:hover {
  color: white;
}

.calculate-btn:disabled {
  border: 1px solid #999999;
  background-color: #cccccc;
  color: #666666;
  cursor: default;
}

.calculate-btn:disabled:hover {
  box-shadow: none;
  transform: translate(0, 0);
}

.data-container {
  background-color: #1f3a67;
  border-radius: 11px;
  margin-top: 40px;
  padding-top: 40px;
  padding-bottom: 40px;
}

.card{
  background-color: #274881 !important;
  color: white;
}

.card-title {
  font-weight: 500 !important;
  text-align: center;
}

.card-data {
  display: flex;
  justify-content: space-around;
}

.delete-btn {
  background-color: #e74c3c;
  color: white;
  border: none;
  border-radius: 50%;
  font-weight: 700;
  padding: 5px 9px;
  cursor: pointer;
  position: absolute;
  top: 0;
  right: 0;

}
.delete-btn:focus {
  background-color: #e74c3c;
}

03输入信息模块

【分支03】

1.定义数据
2.定义,初始化数据状态
3.input 改变时,更新数据(受控组件)
4.提交数据
在这里插入图片描述

03代码

// src\components\App\App.jsx
import React, { useState, useEffect } from 'react';
import 'materialize-css/dist/css/materialize.min.css';
import './App.css'

// 定义数据
const initialValues = {
  weight: '100',
  height: '180',
  data: ''
}

const App = () => {
  // 定义,初始化数据状态
  const [state, setState] = useState(initialValues)

  // input 改变时,更新数据
  const handleChange = e => {
    let { value, name } = e.target;
    setState({
      ...state,
      [name]: value,
    })
  }

  // 提交数据
  const handleSubmit = e => {
    setState(initialValues)
    console.log('已提交', state)
  }

  return (
    <div className='container'>
      {/* 标题 */}
      <div className='row center'>
        <h1 className='white-text'>BMI Tracker</h1>
      </div>


      {/* 输入框 */}
      <div className='row'>
        <div className='col m12 s12'>
          <div className='row'>
            <div className='col m6 s12'>
              <label htmlFor="weight">Weight (in kg)</label>
              <input
                type="number"
                id="weight"
                name="weight"
                min="1"
                max="999"
                placeholder="50"
                value={state.weight}
                onChange={handleChange}
              />
            </div>

            <div className='col m6 s12'>
              <label htmlFor="height">Height (in cm)</label>
              <input
                type="number"
                id="height"
                name="height"
                min="1"
                max="999"
                placeholder="175"
                value={state.height}
                onChange={handleChange}
              />
            </div>
          </div>

          <div className='center'>
            <button
              id="bmi-btn"
              className="calculate-btn"
              type="button"
              disabled={!state.weight || !state.height}
              onClick={handleSubmit}
            >Calculate BMI</button>
          </div>
          { }
        </div>

      </div>

      {/* 统计图 */}
      <div className='row center white-text'>
        统计图
      </div>

      {/* 详细记录信息 */}
      <div>
        <div className='row center'>
          <h2 className='white-text'>7 Day Data</h2>
        </div>

        <div className='data-container row'>
          <div className="col m6 s12">
            <div className="card">
              <div className="card-content">
                <span className="card-title" data-test="bmi">
                  BMI: 20.1
                </span>
                <div className="card-data">
                  <span data-test="weight">Weight: 70 kg</span>
                  <span data-test="height">Height: 180 cm</span>
                  <span data-test="date">Date: 2022/12/12</span>
                </div>
                <button className="delete-btn">X</button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className='center'>
        <button className='calculate-btn'>Undo</button>
      </div>


    </div>
  )
}

export default App;

04 使用图表

chartjs:https://www.chartjs.org/docs/latest/
react-chartjs-2:https://react-chartjs-2.js.org/
主要知道react-chartjs-2怎么使用
【04分支】
在这里插入图片描述

04代码

// src\components\App\App.jsx
import React, { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import 'materialize-css/dist/css/materialize.min.css';
import './App.css'

// 定义数据
const initialValues = {
  weight: '100',
  height: '180',
  data: ''
}

const App = () => {
  // 定义,初始化数据状态
  const [state, setState] = useState(initialValues)

  // input 改变时,更新数据
  const handleChange = e => {
    let { value, name } = e.target;
    setState({
      ...state,
      [name]: value,
    })
  }

  // 提交数据
  const handleSubmit = e => {
    setState(initialValues)
    console.log('已提交', state)
  }

  const labelData = [2021,2022,2023]
  const bmiData = [100,200,300]

  // 定义图标数据
  const data = canvas => {
    // 从传入的canvas元素中获取2D绘图上下文,这是在canvas上绘制图形的基础。
    // 这段代码创建了一个线性渐变对象,起始于坐标(63, 81),结束于(181, 700),颜色从#929dd9渐变到#172b4d。这常用于为图表的填充色提供动态效果。
    const ctx = canvas.getContext("2d");
    const gradient = ctx.createLinearGradient(63, 81, 181, 700);
    gradient.addColorStop(0, '#929dd9');
    gradient.addColorStop(1, '#172b4d');
    return{
      labels: labelData,  // 图表的标签数组,通常对应X轴的各个分类
      datasets: [  // 一个数据集对象
        {
          label: 'BMI',  // 数据集的标签,通常用于图例
          data: bmiData,  // 数据集的实际数值数组,对应Y轴的值。
          backgroundColor: gradient,  // 使用之前创建的gradient作为填充色。
          borderColor: '#3F51B5',  // 数据点的边框颜色为#3F51B5。
          pointRadius: 6,  // 数据点的半径为6。
          pointHoverRadius: 8,  // 鼠标悬停时数据点的半径增大到8。
          pointHoverBorderColor: 'white',  // 鼠标悬停时数据点边框颜色变为白色。
          pointHoverBorderWidth: 2  // 鼠标悬停时数据点边框宽度为2。
        }
      ]
  }
}

  // options 该对象包含了配置信息,主要用来定制基于Chart.js库的图表外观和行为
  const options = {
    responsive: true, // 设置图表是否应响应式
    scales: { //定义图表的坐标轴配置,包括x轴(xAxes)和y轴(yAxes)的样式和行为
      xAxes: [
        {
          scaleLabel: {
            display: true,
            labelString: 'Date',
            fontSize: 18,
            fontColor: 'white'
          },
          gridLines: {
            display: false,
            color: 'white'
          },
          ticks: {
            fontColor: 'white',
            fontSize: 16
          }
        }
      ],
      yAxes: [
        {
          scaleLabel: { //  x轴标题的配置。
            display: true,  // 是否显示x轴标题
            labelString: 'BMI',  // x轴标题的文本内容
            fontSize: 18,  // 标题的字体大小和颜色
            fontColor: 'white'
          },
          gridLines: {  // 网格线的配置
            display: false,  // 不显示x轴的网格线
            color: 'white'  // 格线的颜色,即使不显示也定义了颜色
          },
          ticks: {  // 刻度线的配置
            fontColor: 'white', // 刻度线标签的字体颜色和大小。
            fontSize: 16,
            beginAtZero: true   // 图表的y轴刻度从0开始
          }
        }
      ]
    },
    tooltips: { // 定义图表提示框(tooltip)的样式。
      // 分别设置提示框标题和内容的字体大小。
      titleFontSize: 13,
      bodyFontSize: 13
    }
  }
  return (
    <div className='container'>
      {/* 标题 */}
      <div className='row center'>
        <h1 className='white-text'>BMI Tracker</h1>
      </div>


      {/* 输入框 */}
      <div className='row'>
        <div className='col m12 s12'>
          <div className='row'>
            <div className='col m6 s12'>
              <label htmlFor="weight">Weight (in kg)</label>
              <input
                type="number"
                id="weight"
                name="weight"
                min="1"
                max="999"
                placeholder="50"
                value={state.weight}
                onChange={handleChange}
              />
            </div>

            <div className='col m6 s12'>
              <label htmlFor="height">Height (in cm)</label>
              <input
                type="number"
                id="height"
                name="height"
                min="1"
                max="999"
                placeholder="175"
                value={state.height}
                onChange={handleChange}
              />
            </div>
          </div>

          <div className='center'>
            <button
              id="bmi-btn"
              className="calculate-btn"
              type="button"
              disabled={!state.weight || !state.height}
              onClick={handleSubmit}
            >Calculate BMI</button>
          </div>
          { }
        </div>

      </div>

      {/* 统计图 */}
      <div className='row center white-text'>
        {/* 使用折线图 */}
        <Line data={data} options={options}/>
      </div>

      {/* 详细记录信息 */}
      <div>
        <div className='row center'>
          <h2 className='white-text'>7 Day Data</h2>
        </div>

        <div className='data-container row'>
          <div className="col m6 s12">
            <div className="card">
              <div className="card-content">
                <span className="card-title" data-test="bmi">
                  BMI: 20.1
                </span>
                <div className="card-data">
                  <span data-test="weight">Weight: 70 kg</span>
                  <span data-test="height">Height: 180 cm</span>
                  <span data-test="date">Date: 2022/12/12</span>
                </div>
                <button className="delete-btn">X</button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className='center'>
        <button className='calculate-btn'>Undo</button>
      </div>


    </div>
  )
};
export default App;

解释

const data = canvas => {
    const ctx = canvas.getContext('2d');
    const gradient = ctx.createLinearGradient(63, 81, 181, 700);
    gradient.addColorStop(0, '#929dd9');
    gradient.addColorStop(1, '#172b4d');

    return {
      labels: labelData,
      datasets: [
        {
          label: 'BMI',
          data: bmiData,
          backgroundColor: gradient,
          borderColor: '#3F51B5',
          pointRadius: 6,
          pointHoverRadius: 8,
          pointHoverBorderColor: 'white',
          pointHoverBorderWidth: 2
        }
      ]
    };
  };

这段JavaScript代码定义了一个名为data的函数,它接收一个canvas元素作为参数,并返回一个配置对象,该对象常用于初始化或更新基于Chart.js(或其他类似图表库)的图表数据和样式。下面是代码的详细解释:

  1. 获取2D渲染上下文:
javascript

   const ctx = canvas.getContext('2d');

这行代码从传入的canvas元素中获取2D绘图上下文,这是在canvas上绘制图形的基础。

  1. 创建线性渐变:
javascript   const gradient = ctx.createLinearGradient(63, 81, 181, 700);
   gradient.addColorStop(0, '#929dd9');
   gradient.addColorStop(1, '#172b4d');

这段代码创建了一个线性渐变对象,起始于坐标(63, 81),结束于(181, 700),颜色从#929dd9渐变到#172b4d。这常用于为图表的填充色提供动态效果。

  1. 返回图表配置对象
    返回的对象结构定义了图表的数据和样式,主要包括:
    • labels: labelData,图表的标签数组,通常对应X轴的各个分类。

    • datasets
      包含一个数据集对象,具体定义为:
      • label: 'BMI',数据集的标签,通常用于图例。
      • data: bmiData,数据集的实际数值数组,对应Y轴的值。
      • backgroundColor: 使用之前创建的gradient作为填充色。
      • borderColor: 数据点的边框颜色为#3F51B5
      • pointRadius: 数据点的半径为6。
      • pointHoverRadius: 鼠标悬停时数据点的半径增大到8。
      • pointHoverBorderColor: 鼠标悬停时数据点边框颜色变为白色。
      • pointHoverBorderWidth: 鼠标悬停时数据点边框宽度为2。

综上所述,这个函数用于生成一个配置对象,配置了一种特定样式的图表,其中数据填充色为线性渐变,适合于展示BMI(身体质量指数)等相关数据的图表展示。

options的对象,该对象包含了配置信息,主要用来定制基于Chart.js库的图表外观和行为。具体配置项解释如下:

  • responsive: true: 设置图表是否应响应式,即图表是否会根据其容器的大小自动调整。

  • scales: 定义图表的坐标轴配置,包括x轴(xAxes)和y轴(yAxes)的样式和行为。

    • xAxes: 配置x轴的设置。

      • scaleLabel
        x轴标题的配置。
        • display: true: 是否显示x轴标题。
        • labelString: ‘Date’: x轴标题的文本内容。
        • fontSize: 18fontColor: ‘white’: 标题的字体大小和颜色。
      • gridLines
        网格线的配置。
        • display: false: 不显示x轴的网格线。
        • color: ‘white’: 网格线的颜色,即使不显示也定义了颜色。
      • ticks
        刻度线的配置。
        • fontColor: ‘white’fontSize: 16: 刻度线标签的字体颜色和大小。
    • yAxes: 配置y轴的设置,结构和配置项含义与x轴相似,但多了beginAtZero: true,表示y轴的刻度应该从0开始。

  • tooltips: 定义图表提示框(tooltip)的样式。

    • titleFontSize: 13bodyFontSize: 13: 分别设置提示框标题和内容的字体大小。

整体而言,这段代码详细地定制了一个图表的外观,包括坐标轴的标题、网格线、刻度线的样式,以及提示框的字体大小,使得图表更加符合特定的视觉需求,比如使用白色字体适应深色背景等。

05详细记录信息渲染

【05分支】

1.完整信息数据列表
硬编码,编写两组数据
2.完整信息数据列表渲染到图表
3.详细记录信息渲染

在这里插入图片描述
现在和设计稿已经一样了,

05代码

// src\components\App\App.jsx
import React, { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import 'materialize-css/dist/css/materialize.min.css';
import './App.css'

// 定义数据
const initialValues = {
  weight: '100',
  height: '180',
  data: ''
}

// 完整信息数据列表
const stateS = [
  { "weight": "50", "height": "170", "date": "2024/6/11 20:05:16", "bmi": "17.30", "id": "e4d54aef-0e89-4e7e-a887-9d7a289da5de" },
  { "weight": "51", "height": "170", "date": "2024/6/11 20:05:32", "bmi": "17.65", "id": "a79a7b3c-c1e6-48b3-a2ff-f331db09fa72" }
]

const App = () => {
  // 定义,初始化数据状态
  const [state, setState] = useState(initialValues)

  // input 改变时,更新数据
  const handleChange = e => {
    let { value, name } = e.target;
    setState({
      ...state,
      [name]: value,
    })
  }

  // 提交数据
  const handleSubmit = e => {
    setState(initialValues)
    console.log('已提交', state)
  }

  // 交给图表 显示数据
  const labelData = stateS.map(item => item.date)
  const bmiData = stateS.map(item => item.bmi)

  // 定义图标数据
  const data = canvas => {
    // 从传入的canvas元素中获取2D绘图上下文,这是在canvas上绘制图形的基础。
    // 这段代码创建了一个线性渐变对象,起始于坐标(63, 81),结束于(181, 700),颜色从#929dd9渐变到#172b4d。这常用于为图表的填充色提供动态效果。
    const ctx = canvas.getContext("2d");
    const gradient = ctx.createLinearGradient(63, 81, 181, 700);
    gradient.addColorStop(0, '#929dd9');
    gradient.addColorStop(1, '#172b4d');
    return {
      labels: labelData,  // 图表的标签数组,通常对应X轴的各个分类
      datasets: [  // 一个数据集对象
        {
          label: 'BMI',  // 数据集的标签,通常用于图例
          data: bmiData,  // 数据集的实际数值数组,对应Y轴的值。
          backgroundColor: gradient,  // 使用之前创建的gradient作为填充色。
          borderColor: '#3F51B5',  // 数据点的边框颜色为#3F51B5。
          pointRadius: 6,  // 数据点的半径为6。
          pointHoverRadius: 8,  // 鼠标悬停时数据点的半径增大到8。
          pointHoverBorderColor: 'white',  // 鼠标悬停时数据点边框颜色变为白色。
          pointHoverBorderWidth: 2  // 鼠标悬停时数据点边框宽度为2。
        }
      ]
    }
  }

  // options 该对象包含了配置信息,主要用来定制基于Chart.js库的图表外观和行为
  const options = {
    responsive: true, // 设置图表是否应响应式
    scales: { //定义图表的坐标轴配置,包括x轴(xAxes)和y轴(yAxes)的样式和行为
      xAxes: [
        {
          scaleLabel: {
            display: true,
            labelString: 'Date',
            fontSize: 18,
            fontColor: 'white'
          },
          gridLines: {
            display: false,
            color: 'white'
          },
          ticks: {
            fontColor: 'white',
            fontSize: 16
          }
        }
      ],
      yAxes: [
        {
          scaleLabel: { //  x轴标题的配置。
            display: true,  // 是否显示x轴标题
            labelString: 'BMI',  // x轴标题的文本内容
            fontSize: 18,  // 标题的字体大小和颜色
            fontColor: 'white'
          },
          gridLines: {  // 网格线的配置
            display: false,  // 不显示x轴的网格线
            color: 'white'  // 格线的颜色,即使不显示也定义了颜色
          },
          ticks: {  // 刻度线的配置
            fontColor: 'white', // 刻度线标签的字体颜色和大小。
            fontSize: 16,
            beginAtZero: true   // 图表的y轴刻度从0开始
          }
        }
      ]
    },
    tooltips: { // 定义图表提示框(tooltip)的样式。
      // 分别设置提示框标题和内容的字体大小。
      titleFontSize: 13,
      bodyFontSize: 13
    }
  }
  return (
    <div className='container'>
      {/* 标题 */}
      <div className='row center'>
        <h1 className='white-text'>BMI Tracker</h1>
      </div>


      {/* 输入框 */}
      <div className='row'>
        <div className='col m12 s12'>
          <div className='row'>
            <div className='col m6 s12'>
              <label htmlFor="weight">Weight (in kg)</label>
              <input
                type="number"
                id="weight"
                name="weight"
                min="1"
                max="999"
                placeholder="50"
                value={state.weight}
                onChange={handleChange}
              />
            </div>

            <div className='col m6 s12'>
              <label htmlFor="height">Height (in cm)</label>
              <input
                type="number"
                id="height"
                name="height"
                min="1"
                max="999"
                placeholder="175"
                value={state.height}
                onChange={handleChange}
              />
            </div>
          </div>

          <div className='center'>
            <button
              id="bmi-btn"
              className="calculate-btn"
              type="button"
              disabled={!state.weight || !state.height}
              onClick={handleSubmit}
            >Calculate BMI</button>
          </div>
          { }
        </div>

      </div>

      {/* 统计图 */}
      <div className='row center white-text'>
        {/* 使用折线图 */}
        <Line data={data} options={options} />
      </div>

      {/* 详细记录信息 */}
      <div>
        <div className='row center'>
          <h2 className='white-text'>7 Day Data</h2>
        </div>

        <div className='data-container row'>
          {stateS.length > 0 ? (
            <>
              {stateS.map(info => (
                <div className="col m6 s12">
                  <div className="card">
                    <div className="card-content">
                      <span className="card-title" data-test="bmi">
                        BMI: {info.bmi}
                      </span>
                      <div className="card-data">
                        <span data-test="weight">Weight: {info.weight} kg</span>
                        <span data-test="height">Height: {info.height} cm</span>
                        <span data-test="date">Date: {info.date}</span>
                      </div>
                      <button className="delete-btn">X</button>
                    </div>
                  </div>
                </div>
              ))}
            </>
          ) : (<div className='center white-text'>No log found</div>)}
        </div>
      </div>
      <div className='center'>
        <button className='calculate-btn'>Undo</button>
      </div>


    </div>
  )
};
export default App;

06 让数据变成响应式的

06-1输入框的数据处理

【06-1分支】

问题
在这里插入图片描述
问题代码
在这里插入图片描述
解决
在这里插入图片描述
在这里插入图片描述

每次state变化,都会触发更新

我们在体重,身高输入框,输入
53,175
会往state状态里插入,如下的的一条数据

{weight: '53', height: '175', date: '2024/6/15 21:40:24', bmi: '17.31', id: 'f83452b5-a7b5-4a57-beb1-ea552bf432cb'}

接在下来,我们的图表,和记录信息的区域,都显示这些数据
在这里插入图片描述

06-2图表,和记录信息的区域数据处理

state有几条数据就显示几条
在这里插入图片描述

在这里插入图片描述

07 删除功能,撤销功能

点击x删除对应数据
点击Undo撤销上一步操作(如果上一步是点击x删除对应数据,那么Undo就是回复上一步;如果上一步是添加里一条数据,那么Undo就是删除新添加的这条数据 )
在这里插入图片描述

删除功能完成

在这里插入图片描述

撤销功能

思路就是所在点击删除后,第一件事就是先保存一份最新的state

  // let lastState // 注意这个地方,如只是一般的变量,那么每次setState(lastState),渲染的时候handleUndo函数都会从新执行,一直在初始化lastState,所以需要使用useRef
  let lastState = useRef([])
  const deleteCard = (id) => {
    lastState.current = state.slice();
    let newState = state.filter(item => item.id !== id)
    setState(newState)
    // console.log(id,state)
    // console.log(lastState.current)
  }

  const handleUndo = () => {
    // setState(lastState);
    setState(lastState.current);
    // console.log(lastState.current , state)
  }

到目前位置,我们所有的功能都已完成
在这里插入图片描述

08 数据持久化、组件化、模块化

目前我们我们把这个小应用全部写在了一个文件里,这样文件会显得臃肿,庞大,混乱难以维护。当等功能增多的时候就会,更加庞大,混乱。

所以我们接下来要拆分这个组件,分成一个个小的组件。

现在我们的数据在缓存里,刷新就会丢失。所以我们将数据持久化到本地,关闭浏览器也不会丢失。

08-1数据持久化

【分支08-1】
写一个将数据存储到本地,从本地获取的数据的模块

// src\helpers\localStorage.js
export const getData = (key) => {
	if (!localStorage) return;

	try {
		return JSON.parse(localStorage.getItem(key));
	} catch (err) {
		console.error(`Error getting item ${key} from localStorage`, err);
	}
};

export const storeData = (key, item) => {
	if (!localStorage) return;

	try {
		return localStorage.setItem(key, JSON.stringify(item));
	} catch (err) {
		console.error(`Error storing item ${key} to localStorage`, err);
	}
};

在这里插入图片描述

存数据

  useEffect(() => {
    storeData('data', state);  // 初始化组件和每次更新state时,都会触发storeData保存数据
    console.log('App_state', state)

  }, [state]);
取数据
const App = () => {
  // ,initialState 被定义为一个箭头函数,然后作为 useState 的参数使用。这里有一个常见的误解:通常我们不希望将 useState 的初始化函数定义为箭头函数,因为这样会导致每次组件渲染时都会创建一个新的函数实例,可能会引发不必要的组件重新渲染。
  // initialState是一个箭头函数,这种方式适用于当你想延迟执行 getData('data') 或者在未来的某个时间点决定是否执行这个操作时

  const initialState = () => getData('data') || [];  
  
  const [state, setState] = useState(initialState)
Undo 使用本地化数据,不使用useRef()缓存了
  // let lastState // 注意这个地方,如只是一般的变量,那么每次setState(lastState),渲染的时候handleUndo函数都会从新执行,一直在初始化lastState,所以需要使用useRef
  // let lastState = useRef([])
  const deleteCard = (id) => {
    storeData('lastState', state);  // 不使用useRef([])缓存了,直接本地化保存数据
    let newState = state.filter(item => item.id !== id)
    setState(newState)
    // console.log(id,state)
    // console.log(lastState.current)
  }

  const handleUndo = () => {
    // setState(lastState);
    setState(getData('lastState'));
    // console.log(lastState.current , state)
  }

08-2组件化、模块化

将输入添加,图表模块,七天数据模块,做成单独的模块

输入添加模块
图表模块
七天数据模块

08-2-1输入添加模块

【分支08-2-1】
直接将我写好的都拿过来
Input_handleChange , 从App模块传递到 BmiForm 模块

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import '../App/App.css'

const Input_initialValues = {
	weight: '',
	height: '',
	date: ''
}

const BmiForm = ({ Input_handleChange }) => {
	// 定义,初始化数据状态
	const [Input_state, setState_Input] = useState(Input_initialValues)

	// input 改变时,更新数据
	const handleChange = e => {
		let { value, name } = e.target;
		// 输入的数字不能大于999
		if (value > 999) {
			value = 999
		}
		const date = new Date().toLocaleString().split(',')[0]
		// console.log(date)
		// 更新输入框的值
		setState_Input({
			...Input_state,
			[name]: value,
			date
		})
	}

	  // 提交数据
		const handleSubmit = () => {
			Input_handleChange(Input_state)
			setState_Input(Input_initialValues)
			// console.log('已提交', Input_state)
			// console.log('已提交', Input_initialValues)
		}

	return (
		<>
			{/* 输入框 */}
			< div className='row' >
				<div className='col m12 s12'>
					<div className='row'>
						<div className='col m6 s12'>
							<label htmlFor="weight">Weight (in kg)</label>
							<input
								type="number"
								id="weight"
								name="weight"
								min="1"
								max="999"
								placeholder="50"
								value={Input_state.weight}
								onChange={handleChange}
							/>
						</div>

						<div className='col m6 s12'>
							<label htmlFor="height">Height (in cm)</label>
							<input
								type="number"
								id="height"
								name="height"
								min="1"
								max="999"
								placeholder="175"
								value={Input_state.height}
								onChange={handleChange}
							/>
						</div>
					</div>

					<div className='center'>
						<button
							id="bmi-btn"
							className="calculate-btn"
							type="button"
							disabled={!Input_state.weight || !Input_state.height}
							onClick={handleSubmit}
						>Calculate BMI</button>
					</div>
					{ }
				</div>
			</div >
		</>
	)
}

BmiForm.propTypes ={
	change: PropTypes.func.isRequired
}

export default BmiForm;

08-2-2图表模块

【分支08-2-2】
直接将我写好的都拿过来

08-2-3七天数据模块

【分支08-2-3】
直接将我写好的都拿过来

09 修一些bug

【分支09】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1829596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeepDriving | 经典的目标检测算法:CenterNet

本文来源公众号“DeepDriving”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;经典的目标检测算法&#xff1a;CenterNet 1 前言 CenterNet是2019年发表的一篇文章《Objects as Points》中提出的一个经典的目标检测算法&#xf…

仓储管理系统WMS构架设计B/S和C/S:如何选?

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 在设计仓库管理系统&#xff08;WMS&#xff09;时&#xff0c;架构的选择至关重要&#xff0c;因为它直接影响到系统的可用性、可维护性、灵活性…

17岁中专女生,闯进全球数学竞赛12强

今年阿里的数学竞赛结果出来了&#xff0c;在榜单的前列包含一个 17 岁的中专女生。 在 2018 年时&#xff0c;阿里巴巴达摩院发起了一个国际数学竞赛&#xff0c;基本每年举办一次&#xff0c;参赛不设报名条件&#xff0c;向全球所有数学爱好者开放&#xff0c;竞赛由阿里创…

从FasterTransformer源码解读开始了解大模型(2.1)代码通读02

从FasterTransformer源码解读开始了解大模型&#xff08;2.0&#xff09;代码解读02-初始化和forward 写在前面的话 本篇的内容主要是介绍ParallelGpt.cc中的代码内容&#xff0c;首先介绍一些初始化和工具函数&#xff0c;然后会从forward主函数开始介绍一部分。 零、初始化…

【ROS里程计】中部分代码解释

bool OdomNodePub::Odom_Reset(ubt_odom::odomreset::Request& req, ubt_odom::odomreset::Response& res) {if(req.cmd "reset"){OdomResetFlag true;}else{OdomResetFlag false;}res.state "success";return true; } 该函数是一个ROS节点中…

元数据、数据元、数据字典、数据模型及元模型的区别详解

在数据管理和分析领域&#xff0c;有许多相似的概念&#xff0c;如元数据、数据元、数据字典、数据模型和元模型。这些概念的定义和应用往往容易混淆。 数据元 数据元是通过一系列属性描述的数据单元&#xff0c;包括定义、标识、表示以及允许值等。这些属性帮助我们理解和使用…

aop注解快速实现数据脱敏返回

说明&#xff1a; 公司之前数据接口数据管理不严格&#xff0c;很多接口的敏感数据都没有脱敏处理&#xff0c;直接返回给前端了&#xff0c;然后被甲方的第三方安全漏洞扫出来&#xff0c;老板要求紧急处理&#xff0c;常用的话在单个字段上加上脱敏注解会更加的灵活&#xf…

Parallels Desktop 19 for mac破解版安装激活使用指南

Parallels Desktop 19 for Mac 乃是一款适配于 Mac 的虚拟化软件。它能让您在 Mac 计算机上同时运行多个操作系统。您可借此创建虚拟机&#xff0c;并于其中装设不同的操作系统&#xff0c;如 Windows、Linux 或 macOS。使用 Parallels Desktop 19 mac 版时&#xff0c;您可在 …

EarMaster7.5.74官方版安装激活使用教程

EarMaster就是你音乐路上的良师益友。这是一款来自丹麦皇家音乐学院的多媒体音乐教育软件&#xff0c;针对视唱练耳为音乐学生&#xff0c;音乐爱好者以及音乐专业人员都带来了很多的帮助&#xff0c;让你们可以获得音乐家般的耳朵&#xff0c;通过专业视唱练耳培训考试&#x…

52. QT插件开发--插件程序(带ui文件)的创建与编译

1. 说明 一般情况下,针对代码量比较小的QT程序不需要进行插件集成化开发,但是针对大型程序来说,代码结构比较复杂,使用插件开发的方式可以提高代码开发和维护效率,团队之间的分工合作也会更加的明确。所谓插件式开发,实际上就是把程序的一部分功能封装起来,编译成一个单…

Modbus为何要转成ProfiNET

Modbus与ProfiNET代表了工业通讯不同阶段的发展&#xff0c;各自具有优缺点。Modbus简单易用&#xff0c;适合小型系统&#xff1b;ProfiNET高效稳定&#xff0c;适用于大型复杂网络。转换Modbus为ProfiNET可提高系统性能和扩展性。实际场景下&#xff0c;升级生产线控制器为Pr…

Cisco Packet Tracer实验(四)

生成树协议&#xff08;Spanning Tree Protocol&#xff09; 交换机在目的地址未知或接收到广播帧时是要进行广播的。如果交换机之间存在回路/环路&#xff0c;那么就会产生广播循环风暴&#xff0c;从而严重影响网络性能。 而交换机中运行的STP协议能避免交换机之间发生广播…

Python(三)---字符串

文章目录 前言1.创建字符串2.字符串的编码3.空字符串和len()函数4.转义字符5.从控制台读取字符串6.字符串的相关操作6.1.通过[]访问元素6.2.字符串切片slice操作6.3.字符串拼接和字符串复制6.4.split()分割和join()合并6.5.常用查找方法6.6.replace() 实现字符串替换6.7.去除首…

基于CPS-SPWM链式STATCOM系统在电压不平衡环境下控制策略的simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于CPS-SPWM链式STATCOM系统在电压不平衡环境下控制策略的simulink建模与仿真。利用电压外环PI调节器得到有功 电流指令值结合由负载侧电流检测 到 的无功 电流指令值 &#…

GPU的工作原理

location: Beijing 1. why is GPU CPU的存储单元和计算单元的互通过慢直接促进了GPU的发展 先介绍一个概念&#xff1a;FLOPS&#xff08;Floating Point Operations Per Second&#xff0c;浮点运算每秒&#xff09;是一个衡量其执行浮点运算的能力&#xff0c;可以作为计算…

Gstreamer学习3----灌数据给管线之appsrc

参考资料 Basic tutorial 8: Short-cutting the pipeline gstreamer向appsrc发送帧画面的代码_gst appsrc可变帧率-CSDN博客 在官网教程Basic tutorial 8: Short-cutting the pipeline 里面&#xff0c;讲了一个例子&#xff0c;push音频数据给管线&#xff0c;视频的例子更…

归纳贪心好题

很有趣的一道归纳贪心题目 class Solution { public:int minimumAddedCoins(vector<int>& coins, int target) {sort(coins.begin(),coins.end());int n coins.size();int s 0,i0;int res 0;while(s<target){if(i<n&&coins[i]<s1)scoins[i];els…

Photoshop中图像美化工具的应用

Photoshop中图像美化工具的应用 Photoshop中的裁剪工具Photoshop中的修饰工具模糊工具锐化工具涂抹工具 Photoshop中的颜色调整工具减淡工具加深工具海绵工具 Photoshop中的修复工具仿制图章工具污点修复画笔工具修复画笔工具修补工具内容感知移动工具红眼工具 Photoshop中的裁…

Ubuntu 的 apt 相关问题

错误:1 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal InRelease Couldnt create temporary file /tmp/apt.conf.KSeTlI for passing config to apt-key 原因 无法创建配置文件 /tmp/apt.conf.KSeTlI 并传递给 apt-key apt-key 等实际上并不是直接使…

效果超越ControlNet+IP-Adapter和FreeControl!Ctrl-X:可控文生图新框架(加州大学英伟达)

文章链接&#xff1a;https://arxiv.org/pdf/2406.07540 项目链接&#xff1a;https://genforce.github.io/ctrl-x/ 最近的可控生成方法&#xff0c;如FreeControl和Diffusion Self-guidance&#xff0c;为文本到图像&#xff08;T2I&#xff09;扩散模型带来了细粒度的空间…