項目演示地址php
項目源碼css
視頻教程html
Vue版本node
小程序版本react
React 框架的優雅不言而喻,組件化的編程思想使得React框架開發的項目代碼簡潔,易懂,但早期 React 類組件的寫法略顯繁瑣。React Hooks 是 React 16.8 發佈以來最吸引人的特性之一,她簡化了原有代碼的編寫,是將來 React 應用的主流寫法。ios
本文經過一個實戰小項目,手把手從零開始帶領你們快速入門React Hooks。本項目線上演示地址:git
在本項目中,會用到如下知識點:npm
(1)安裝node.js 官網連接編程
(2)安裝vscode 官網連接json
(3)安裝 creat-react-app 功能組件,該組件能夠用來初始化一個項目, 即按照必定的目錄結構,生成一個新項目。
打開cmd 窗口 輸入:
npm install --g create-react-app npm install --g yarn
(-g 表明全局安裝)
若是安裝失敗或較慢。須要換源,可使用淘寶NPM鏡像,設置方法爲:
npm config set registry https://registry.npm.taobao.org
設置完成後,從新執行
npm install --g create-react-app npm install --g yarn
(4)在你想建立項目的目錄下 例如 D:/project/ 打開cmd命令 輸入
create-react-app react-exam
去使用creat-react-app命令建立名字是react-exam的項目
安裝完成後,移至新建立的目錄並啓動項目
cd react-exam yarn start
一旦運行此命令,localhost:3000新的React應用程序將彈出一個新窗口。
右鍵react-exam目錄,使用vscode打開該目錄。
react-exam項目目錄中有一個/public和/src目錄,以及node_modules,.gitignore,README.md,和package.json。
在目錄/public
中,重要文件是index.html
,其中一行代碼最重要
<div id="root"></div>
該div作爲咱們整個應用的掛載點
/src
目錄將包含咱們全部的React代碼。
要查看環境如何自動編譯和更新您的React代碼,請找到文件/src/App.js
:
將其中的
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a>
修改成
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > 和豆約翰 Learn React </a>
保存文件後,您會注意到localhost:3000
編譯並刷新了新數據。
1.安裝項目依賴,在package.json中添加:
"dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-scripts": "3.4.1", "axios": "^0.19.2", "bootstrap": "^4.5.0", "he": "^1.2.0", "react-loading": "^2.0.3", "reactstrap": "^8.4.1" },
執行命令:
yarn install
修改index.js,導入bootstrap樣式
import "bootstrap/dist/css/bootstrap.min.css";
修改App.css代碼
html { width: 80%; margin-left: 10%; margin-top: 2%; } .ansButton { margin-right: 4%; margin-top: 4%; }
修改App.js,引入Quiz組件
import React from 'react'; import './App.css' import { Quiz } from './Exam/Quiz'; function App() { return ( <div className = 'layout'> <Quiz></Quiz> </div> ); } export default App;
在項目src目錄下新增Exam目錄,Exam目錄中新建Quiz.js
Quiz組件的定義以下:
Quiz.js,引入開始頁面組件Toggle。
import React, { useState } from "react"; import { Toggle } from "./Toggle"; export const Quiz = () => { const [questionData, setQuestionData] = useState([]); const questions = questionData.map(({ question }) => [question]); const answers = questionData.map(({ incorrect_answers, correct_answer }) => [correct_answer, incorrect_answers].flat() ); return ( <> <Toggle setQuestionData={setQuestionData} /> </> ); };
Toggle.js,點擊開始按鈕,經過axios訪問遠程接口,得到題目及答案。
import React from "react"; import axios from "axios"; import ToggleHeader from "./ToggleHeader"; import { Button, Form, } from "reactstrap"; export const Toggle = ({ setQuestionData, }) => { const getData = async () => { try { const incomingData = await axios.get( `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple` ); setQuestionData(incomingData.data.results); } catch (err) { console.error(err); } }; return ( <> <ToggleHeader /> <Form onSubmit={(e) => { e.preventDefault(); getData(); }} > <Button color="primary">開始</Button> </Form> </> ); };
ToggleHeader.js
import React from "react"; import { Jumbotron, Container} from "reactstrap"; export default function ToggleHeader() { return ( <Jumbotron fluid> <Container fluid> <h1 className="display-4">計算機知識小測驗</h1> </Container> </Jumbotron> ); }
https://opentdb.com/api.php接口返回的json數據格式爲
{ "response_code": 0, "results": [{ "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "The numbering system with a radix of 16 is more commonly referred to as ", "correct_answer": "Hexidecimal", "incorrect_answers": ["Binary", "Duodecimal", "Octal"] }, { "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "This mobile OS held the largest market share in 2012.", "correct_answer": "iOS", "incorrect_answers": ["Android", "BlackBerry", "Symbian"] }, { "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "How many values can a single byte represent?", "correct_answer": "256", "incorrect_answers": ["8", "1", "1024"] }, { "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "In computing, what does MIDI stand for?", "correct_answer": "Musical Instrument Digital Interface", "incorrect_answers": ["Musical Interface of Digital Instruments", "Modular Interface of Digital Instruments", "Musical Instrument Data Interface"] }, { "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "In computing, what does LAN stand for?", "correct_answer": "Local Area Network", "incorrect_answers": ["Long Antenna Node", "Light Access Node", "Land Address Navigation"] }] }
程序運行效果:
當前項目目錄結構爲:
Quiz.js,新增toggleView變量用來切換視圖。
const [toggleView, setToggleView] = useState(true);
Quiz.js,其中Question和QuestionHeader 組件,參見後面。
import { Question } from "./Question"; import { Jumbotron } from "reactstrap"; import QuestionHeader from "./QuestionHeader"; ... export const Quiz = () => { var [index, setIndex] = useState(0); const [questionData, setQuestionData] = useState([]); ... return ( <> {toggleView && ( <Toggle setIndex={setIndex} setQuestionData={setQuestionData} setToggleView={setToggleView} /> )} {!toggleView && ( <Jumbotron> <QuestionHeader setToggleView={setToggleView} /> <Question question={questions[index]} /> </Jumbotron> )} </> );
使用index控制題目索引
var [index, setIndex] = useState(0);
修改Toggle.js
獲取完遠程數據,經過setToggleView(false);切換視圖。
export const Toggle = ({ setQuestionData, setToggleView, setIndex, }) => { ... return ( <> <ToggleHeader /> <Form onSubmit={(e) => { e.preventDefault(); getData(); setToggleView(false); setIndex(0); }} > <Button color="primary">開始</Button> </Form> </> ); };
QuestionHeader.js代碼:
一樣的,點擊 返回首頁按鈕 setToggleView(true),切換視圖。
import React from "react"; import { Button } from "reactstrap"; export default function QuestionHeader({ setToggleView, category }) { return ( <> <Button color="link" onClick={() => setToggleView(true)}> 返回首頁 </Button> </> ); }
Question.js代碼
接受父組件傳過來的question對象,並顯示。
其中he.decode是對字符串中的特殊字符進行轉義。
import React from "react"; import he from "he"; export const Question = ({ question }) => { // he is a oddly named library that decodes html into string values var decode = he.decode(String(question)); return ( <div> <hr className="my-2" /> <h1 className="display-5"> {decode} </h1> <hr className="my-2" /> <br /> </div> ); };
程序運行效果:
首頁
點擊開始後,顯示問題:
當前項目目錄結構爲:
新增LoadingSpin.js
import React from "react"; import { Spinner } from "reactstrap"; export default function LoadingSpin() { return ( <> <Spinner type="grow" color="primary" /> <Spinner type="grow" color="secondary" /> <Spinner type="grow" color="success" /> <Spinner type="grow" color="danger" /> </> ); }
修改Quiz.js
import LoadingSpin from "./LoadingSpin"; export const Quiz = () => { const [isLoading, setLoading] = useState(false); return ( <> {toggleView && ( <Toggle ... setLoading={setLoading} /> )} {!toggleView && (isLoading ? ( <LoadingSpin /> ) : ( ... ))} </> ); };
修改Toggle.js
export const Toggle = ({ ... setLoading, }) => { const getData = async () => { try { setLoading(true); const incomingData = await axios.get( `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple` ); setQuestionData(incomingData.data.results); setLoading(false); } catch (err) { console.error(err); } }; ... };
運行效果:
目前代碼結構:
新增Answer.js,用戶點擊下一題按鈕,修改index,觸發主界面刷新,顯示下一題:
import React from "react"; import { Button } from "reactstrap"; export const Answer = ({ setIndex, index }) => { function answerResult() { setIndex(index + 1); } return ( <Button className="ansButton" onClick={answerResult}> 下一題 </Button> ); };
修改Quiz.js,添加Answer組件:
import { Answer } from "./Answer"; ... {!toggleView && (isLoading ? ( <LoadingSpin /> ) : ( <Jumbotron> ... <Answer setIndex={setIndex} index={index} /> </Jumbotron> ))}
運行效果:
點擊下一題:
新增AnswerList.js。
經過屬性answers傳進來的選項列表,須要被打亂順序(shuffle )
import React from "react"; import { Answer } from "./Answer"; export const AnswerList = ({ answers, index, setIndex }) => { if (answers) var correctAns = answers[0]; const shuffle = (array) => { return array.sort(() => Math.random() - 0.5); }; const arrayCheck = (arg) => { return Array.isArray(arg) ? arg : []; }; return ( <> {shuffle(arrayCheck(answers)).map((text,ind) => ( <Answer text={text} correct={correctAns} setIndex={setIndex} index={index} key={ind} /> ))} </> ); };
修改Answer.js
import React from "react"; import he from "he"; import { Button } from "reactstrap"; export const Answer = ({ text, correct, setIndex, index }) => { function answerResult() { setIndex(index + 1); } var decode = he.decode(String(text)); return ( <Button className="ansButton" onClick={answerResult}> {decode} </Button> ); };
修改Quiz.js
// import { Answer } from "./Answer"; import { AnswerList } from "./AnswerList"; export const Quiz = () => { ... return ( <> ... {!toggleView && (isLoading ? ( <LoadingSpin /> ) : ( ... <AnswerList answers={answers[index]} index={index} setIndex={setIndex} /> </Jumbotron> ))} </> ); };
運行效果:
項目結構:
修改quiz.js,添加setResult,並傳遞給AnswerList
export const Quiz = () => { var [result, setResult] = useState(null); ... return ( <> ... {!toggleView && (isLoading ? ( <LoadingSpin /> ) : ( <Jumbotron> ... <AnswerList answers={answers[index]} index={index} setIndex={setIndex} setResult={setResult} /> </Jumbotron> ))} </> ); };
修改AnswerList.js,傳遞setResult
import React from "react"; import { Answer } from "./Answer"; export const AnswerList = ({ answers, index,setResult, setIndex }) => { ... return ( <> {shuffle(arrayCheck(answers)).map((text,ind) => ( <Answer text={text} correct={correctAns} setIndex={setIndex} setResult={setResult} index={index} key={ind} /> ))} </> ); };
修改Answer.js,用戶點擊選項,回調setResult,通知Quiz組件,本次選擇是對是錯。
import React from "react"; import { Button } from "reactstrap"; import he from 'he' export const Answer = ({ text, correct, setResult,setIndex, index }) => { function answerResult() { setIndex(index + 1); correct === text ? setResult(true) : setResult(false); } var decode = he.decode(String(text)); return ( <Button className="ansButton" onClick={answerResult}> {decode} </Button> ); };
修改Quiz.js,放一個隱藏的GameOver組件,每當index發生變化的時候,觸發GameOver中的useEffect代碼,累計用戶答對題目的數目(setRight)
import GameOver from "./GameOver"; export const Quiz = () => { const [right, setRight] = useState(0); const [gameIsOver, setGameOver] = useState(false); return ( <> {toggleView && ( <Toggle setIndex={setIndex} setQuestionData={setQuestionData} setToggleView={setToggleView} setLoading={setLoading} /> )} {!toggleView && (isLoading ? ( <LoadingSpin /> ) : ( <Jumbotron> <QuestionHeader setToggleView={setToggleView} /> <Question question={questions[index]} /> <AnswerList answers={answers[index]} index={index} setIndex={setIndex} setResult={setResult} /> </Jumbotron> ))} <GameOver right={right} setRight={setRight} quizLength={questions.length} setGameOver={setGameOver} result={result} index={index} /> </> ); };
新增GameOver.js組件,當index === quizLength && index時,setGameOver(true)設置遊戲結束,顯示用戶得分。
import React, { useEffect } from "react"; export default function GameOver({ right, setRight, setGameOver, index, quizLength, result, }) { useEffect(() => { if (result === true) { setRight(right + 1); } if (index === quizLength && index) { setGameOver(true); } }, [index]); return <div></div>; }
新增ScoreBoard.js
import React from "react"; export const ScoreBoard = ({ finalScore, right }) => { // if index === 0 then right === 0 --> this way when index is reset in toggle so is right answers const scoreFormatted = score => { if (score === 1) { return 100; } else if (score === 0) { return 0; } else { return score.toFixed(2) * 100; } } return ( <> <> <h1 className="display-4">Correct Answers: {right}</h1> <hr className="my-2" /> <h1 className="display-4"> Final Score: %{scoreFormatted(finalScore)} </h1> <hr className="my-2" /> </> <p>謝謝使用 </p> </> ); };
ScoreHeader.js
import React from "react"; import { Button } from "reactstrap"; export default function ScoreHeader({ setGameOver, setToggleView }) { return ( <Button color="link" onClick={() => { setGameOver(false); setToggleView(true); }} > 返回首頁 </Button> ); }
修改Quiz.js,當gameIsOver 變量爲true時,顯示得分頁面。
import { ScoreBoard } from "./ScoreBoard"; import ScoreHeader from "./ScoreHeader"; export const Quiz = () => { ... return ( <> {!toggleView && !gameIsOver && (isLoading ? ( <LoadingSpin /> ) : ( ... ))} {gameIsOver && ( <Jumbotron> <ScoreHeader setToggleView={setToggleView} setGameOver={setGameOver} /> <ScoreBoard right={right} finalScore={right / index} /> </Jumbotron> )} ... </> ); };