通过编写一个计算器 来加深 对 react hook 和 context api 的理解
![a9c1294547c36b883cb24f84f7740732.png](https://i-blog.csdnimg.cn/blog_migrate/2a03418c101e62baa8db372ae707774a.jpeg)
我们要实现上面这样的计算器
第一步
利用 create-react-app 快速创建项目
npx create-react-app calculatorcd
随后你会得到一个这样的目录
![475a5ffda7101cac644961aac040a418.png](https://i-blog.csdnimg.cn/blog_migrate/e5ac56213d95bcb73baf7f0a1370198a.png)
第二步 构建程序主体结构
我们创建一个Calculator.js 的文件这个 文件里面需要 有计算器的展示显示屏,还有数字操作面板
import React from 'react';
import NumberButton from './NumberButton';
import FunctionButton from './FunctionButton';
import ClearButton from './ClearButton';
import Display from './Display';
import EqualButton from './EqualButton';
import BackButton from './BackButton';
import NegativeButton from './NegativeButton';
import { CalculatorStyles } from './styles/Styles';
const Calculator = () => (
<CalculatorStyles>
//计算器展示样式
<div className="display">
<h1>计算器</h1>
//展示区
<Display />
</div>
//操作区
<div className="number-pad">
<ClearButton /> //清空按钮
<BackButton /> // 回退按钮
<NegativeButton /> //正负号 按钮
<FunctionButton buttonValue="/" /> //功能按钮 除法
<NumberButton buttonValue={7} /> //数字按钮7 后面类似
<NumberButton buttonValue={8} />
<NumberButton buttonValue={9} />
<FunctionButton buttonValue="*" />
<NumberButton buttonValue={4} />
<NumberButton buttonValue={5} />
<NumberButton buttonValue={6} />
<FunctionButton buttonValue="-" />
<NumberButton buttonValue={1} />
<NumberButton buttonValue={2} />
<NumberButton buttonValue={3} />
<FunctionButton buttonValue="+" />
<div className="zero-button">
<NumberButton buttonValue={0} />
</div>
<NumberButton buttonValue="." />
<EqualButton /> //等于按钮
</div>
</CalculatorStyles>
);
好啦程序主体框架已经完成 下面我们分别来实现这些组件
首先是 CalculatorStyles
也就是计算器的样式表,这里使用了这个插件需要额外安装上
yarn add styled-components -s
//style.js code
import styled from 'styled-components';
export const CalculatorStyles = styled.div`
background-color: #4abdac;
width: 100%;
min-height: 100vh;
display: grid;
justify-items: center;
grid-template-rows: minmax(200px 350px) 1fr;
grid-template-columns: 1fr;
@media (max-width: 500px) {
max-width: 90%;
padding: 5%;
}
.display {
font-family: 'Orbitron', serif;
/* grid-area: display; */
margin: 0 !important;
width: 100%;
@media (max-width: 500px) {
width: 100%;
max-height: 200px;
}
h1 {
font-size: 4rem;
color: white;
text-align: center;
@media (max-width: 500px) {
font-size: 2rem;
}
}
}
.number-pad {
/* grid-area: numbers; */
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(4, 1fr);
padding: 0px 0px 30px;
width: 450px;
margin: 0 auto;
@media (max-width: 500px) {
width: 100%;
margin: 0;
}
button {
width: 100%;
height: 80px;
border-radius: 20px;
border: 3px solid white;
font-size: 2rem;
color: white;
font-family: 'Orbitron', serif;
background: #e17055;
&:focus {
outline: none;
}
&:hover {
border: 3px solid #dfe6e9;
font-weight: 500;
}
}
button.function-button {
background-color: #2d3436;
}
button.white-button {
color: #2d3436;
background-color: white;
}
}
.zero-button {
grid-column: 1/3;
}
`;
export const DisplayStyles = styled.div`
display: grid;
grid-template-rows: 90px 50px;
grid-template-columns: 1fr;
border: 4px solid white;
max-width: 700px;
margin: 10px auto;
align-items: center;
border-radius: 20px;
background: #e17055;
@media (max-width: 500px) {
width: 95%;
grid-template-rows: 60px 40px;
}
h2,
p {
text-align: center;
color: white;
}
h2 {
font-size: 2.5rem;
margin: 0;
text-align: right;
border-bottom: 4px solid white;
padding: 15px 20px;
@media (max-width: 500px) {
font-size: 1.5rem;
padding: 10px;
}
}
h2.long-main-display {
font-size: 1.2rem;
}
p {
margin: 5px 0;
font-size: 1.3rem;
@media (max-width: 500px) {
font-size: 0.8rem;
}
}
p.long-stored-display {
font-size: 0.5rem;
}
`;
接下来是实现计算器的展示区域
![606d7c6cbfa25f613679d1430783e6ea.png](https://i-blog.csdnimg.cn/blog_migrate/8409964ddc0d2b71c64fc7353c033d0b.png)
import React, { useContext } from 'react';
import { NumberContext } from './NumberProvider';
import { DisplayStyles } from './styles/Styles';
const Display = () => {
//这里是调用 我们Context Provider 中提供的 方法和数据 在这里进行消费
const { number, storedNumber, functionType } = useContext(NumberContext);
return (
<DisplayStyles>
//计算结果的展示
<h2 className={storedNumber && storedNumber.length > 12 ? 'long-main-display' : undefined}>
{!number.length && !storedNumber ? '0' : number || storedNumber}
</h2>
//计算式的展示
<p className={storedNumber && storedNumber.length > 12 ? 'long-stored-display' : undefined}>
{!storedNumber ? '输入一些数字进行计算' : `${storedNumber} ${functionType} ${number}`}
</p>
</DisplayStyles>
);
};
export default Display;
接下来是实现操作区域
![75fb37eac9b34ede67be8479016a6bca.png](https://i-blog.csdnimg.cn/blog_migrate/3b45e3ca168e2025dfe1f23d67bfb533.png)
<div className="number-pad">
<ClearButton /> //清空按钮
<BackButton /> // 回退按钮
<NegativeButton /> //正负号 按钮
<FunctionButton buttonValue="/" /> //功能按钮 除法
<NumberButton buttonValue={7} /> //数字按钮7 后面类似
<NumberButton buttonValue={8} />
<NumberButton buttonValue={9} />
<FunctionButton buttonValue="*" />
<NumberButton buttonValue={4} />
<NumberButton buttonValue={5} />
<NumberButton buttonValue={6} />
<FunctionButton buttonValue="-" />
<NumberButton buttonValue={1} />
<NumberButton buttonValue={2} />
<NumberButton buttonValue={3} />
<FunctionButton buttonValue="+" />
<div className="zero-button">
<NumberButton buttonValue={0} />
</div>
<NumberButton buttonValue="." />
<EqualButton /> //等于按钮
</div>
可以看到操作区域主要分为功能按钮 <FunctionButton buttonValue="/" />
和数字按钮 <NumberButton buttonValue={7} />,
还有特殊功能按钮 比如<ClearButton />
对于特殊功能按钮 我们只讲一个因为其他的都类似只是调用的方法不同而已
下面是ClearButton 的实现
import React, { useContext } from 'react';
import { NumberContext } from './NumberProvider';
const ClearButton = () => {
//这里利用Context调用了 清空方法
const { handleClearValue } = useContext(NumberContext);
return (
<button type="button" className="white-button" onClick={() => handleClearValue()}>
C
</button>
);
};
export default ClearButton;
FunctionButton 的实现、
import React, { useContext } from 'react';
import { NumberContext } from './NumberProvider';
const FunctionButton = ({ buttonValue }) => {
//这里利用Context调用了 设置数字计算方式的方法并将参数传入
const { handleSetCalcFunction } = useContext(NumberContext);
return (
<button className="function-button" type="button" onClick={() => handleSetCalcFunction(buttonValue)}>
{buttonValue}
</button>
);
};
export default FunctionButton;
NumberButton 的实现
import React, { useContext } from 'react';
import { NumberContext } from './NumberProvider';
const CalculatorButton = ({ buttonValue }) => {
//这里是设置数字
const { handleSetDisplayValue } = useContext(NumberContext);
return (
<button type="button" onClick={() => handleSetDisplayValue(buttonValue)}>
{buttonValue}
</button>
);
};
export default CalculatorButton;
主体结构已经完成,但是上面那些context 提供的方法还没有实现,由于context的特性它可以使我们可以轻松的在父组件中提供的值很方便的传递给下面的子组件,如果没有这个那么我们只能利用REDUX代替 context 或者 props 一级一级的传递.
Context NumberProvider的实现
import React, { useState } from 'react';
//创建Context
export const NumberContext = React.createContext();
const NumberProvider = props => {
const [number, setNumber] = useState('');//数字
const [storedNumber, setStoredNumber] = useState('');//存储数字
const [functionType, setFunctionType] = useState('');//类型
方法 具体实现
const handleSetDisplayValue = num => {
if ((!number.includes('.') || num !== '.') && number.length < 8) {
setNumber(`${(number + num).replace(/^0+/, '')}`);
}
};
方法 具体实现
const handleSetStoredValue = () => {
setStoredNumber(number);
setNumber('');
};
//清空操作
const handleClearValue = () => {
setNumber('');
setStoredNumber('');
setFunctionType('');
};
方法 具体实现
const handleBackButton = () => {
if (number !== '') {
const deletedNumber = number.slice(0, number.length - 1);
setNumber(deletedNumber);
}
};
方法 具体实现
const handleSetCalcFunction = type => {
if (number) {
setFunctionType(type);
handleSetStoredValue();
}
if (storedNumber) {
setFunctionType(type);
}
};
方法 具体实现
const handleToggleNegative = () => {
if (number) {
if (number > 0) {
setNumber(`-${number}`);
} else {
const positiveNumber = number.slice(1);
setNumber(positiveNumber);
}
} else if (storedNumber > 0) {
setStoredNumber(`-${storedNumber}`);
} else {
const positiveNumber = storedNumber.slice(1);
setStoredNumber(positiveNumber);
}
};
方法 具体实现
const doMath = () => {
if (number && storedNumber) {
switch (functionType) {
case '+':
setStoredNumber(`${Math.round(`${(parseFloat(storedNumber) + parseFloat(number)) * 100}`) / 100}`);
break;
case '-':
setStoredNumber(`${Math.round(`${(parseFloat(storedNumber) - parseFloat(number)) * 1000}`) / 1000}`);
break;
case '/':
setStoredNumber(`${Math.round(`${(parseFloat(storedNumber) / parseFloat(number)) * 1000}`) / 1000}`);
break;
case '*':
setStoredNumber(`${Math.round(`${parseFloat(storedNumber) * parseFloat(number) * 1000}`) / 1000}`);
break;
default:
break;
}
setNumber('');
}
};
return (
//子组件就是通过这个来和父组件进行通信,我们可以看到这里提供的方法和数字 和 子组件中 的useContext Hook 可以对应上
<NumberContext.Provider
value={{
doMath,
functionType,
handleBackButton,
handleClearValue,
handleSetCalcFunction,
handleSetDisplayValue,
handleSetStoredValue,
handleToggleNegative,
number,
storedNumber,
setNumber,
}}
>
{props.children}
</NumberContext.Provider>
);
};
export default NumberProvider;
好了 程序完成 执行 yarn start 看看效果把
源代码