<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從零開始使用create-react-app + react + typescript 完成一個(gè)網(wǎng)站

          共 73877字,需瀏覽 148分鐘

           ·

          2021-10-02 09:43

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群


          在線示例

          以下是一個(gè)已經(jīng)完成的成品,如圖所示:

          img

          你也可以點(diǎn)擊此處:https://www.eveningwater.com/my-web-projects/react/5/查看在線示例。

          也許有人咋一看,看到這個(gè)網(wǎng)站有些熟悉,沒錯(cuò),這個(gè)網(wǎng)站來源于https://jsisweird.com/。我花了三天時(shí)間,用create-react-app + react + typescript重構(gòu)這個(gè)網(wǎng)站,與網(wǎng)站效果不同的是,我沒有加入任何的動(dòng)畫,并且我添加了中英文切換以及回到頂部的效果。

          設(shè)計(jì)分析

          觀看整個(gè)網(wǎng)站,其實(shí)整體的架構(gòu)也不復(fù)雜,就是一個(gè)首頁,20道問題頁面以及一個(gè)解析頁面構(gòu)成。這些涉及到的問題也好,標(biāo)題也罷,其實(shí)都是一堆定義好的數(shù)據(jù),下面我們來一一查看這些數(shù)據(jù)的定義:

          問題數(shù)據(jù)的定義

          很顯然,問題數(shù)據(jù)是一個(gè)對象數(shù)組,我們來看結(jié)構(gòu)如下:

           export const questions = []; 
           //因?yàn)閱栴}本身不需要實(shí)現(xiàn)中英文切換,所以我們這里也不需要區(qū)分,數(shù)組項(xiàng)的結(jié)構(gòu)如:{question:"true + false",answer:["\"truefalse\"","1","NaN","SyntaxError"],correct:"1"},

          數(shù)據(jù)的表示一眼就可以看出來,question代表問題,answer代表回答選項(xiàng),correct代表正確答案。讓我們繼續(xù)。

          解析數(shù)據(jù)的定義

          解析數(shù)據(jù),需要進(jìn)行中英文切換,所以我們用一個(gè)對象表示,如下:

          export const parseObject = {
              "en":{
                  output:"",//輸出文本
                  answer:"",//用戶回答文本:[],
                  successMsg:"",//用戶回答正確文本
                  errorMsg:"",//用戶回答錯(cuò)誤文本
                  detail:[],//問題答案解析文本
                  tabs:[],//中英文切換選項(xiàng)數(shù)組
                  title:"",//首頁標(biāo)題文本
                  startContent:"",//首頁段落文本
                  endContent:"",//解析頁段落文本
                  startBtn:"",//首頁開始按鈕文本
                  endBtn:"",//解析頁重新開始文本
              },
              "zh":{
                  //選項(xiàng)同en屬性值一致
              }
          }

          更多詳情,請查看此處源碼:https://github.com/eveningwater/my-web-projects/blob/master/react/5/src/data/data.ts。

          這其中,由于detail里的數(shù)據(jù)只是普通文本,我們需要將其轉(zhuǎn)換成HTML字符串,雖然有marked.js這樣的庫可以幫助我們,但是這里我們的轉(zhuǎn)換規(guī)則也比較簡單,無需使用marked.js這樣的庫,因此,我在這里封裝了一個(gè)簡易版本的marked工具函數(shù),如下所示:

          export function marked(template) {
          let result = "";
          result = template.replace(/\[.+?\]\(.+?\)/g,word => {
          const link = word.slice(word.indexOf('(') + 1, word.indexOf(')'));
          const linkText = word.slice(word.indexOf('[') + 1, word.indexOf(']'));
          return `<a href="${link}" target="blank">${linkText}</a>`;
          }).replace(/\*\*\*([\s\S]*?)\*\*\*[\s]?/g,text => '<code>' + text.slice(3,text.length - 4) + '</code>');
          return result;
          }

          轉(zhuǎn)換規(guī)則也比較簡單,就是匹配a標(biāo)簽以及code標(biāo)簽,這里我們寫的是類似markdown的語法。比如a標(biāo)簽的寫法應(yīng)該是如下所示:

          [xxx](xxx)

          所以以上的轉(zhuǎn)換函數(shù),我們匹配的就是這種結(jié)構(gòu)的字符串,其正則表達(dá)式結(jié)構(gòu)如:

          /\[.+?\]\(.+?\)/g;

          這其中.+?表示匹配任意的字符,這個(gè)正則表達(dá)式就不言而喻了。除此之外,我們匹配代碼高亮的markdown的語法定義如下:

          ***//code***

          為什么我要如此設(shè)計(jì)?這是因?yàn)槿绻乙彩褂?code style="font-size: 14px;border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">markdown的三個(gè)模板字符串符號(hào)來定義代碼高亮,會(huì)和js的模板字符串起沖突,所以為了不必要的麻煩,我改用了三個(gè)*來表示,所以以上的正則表達(dá)式才會(huì)匹配*。如下:

          /\*\*\*([\s\S]*?)\*\*\*[\s]?/g

          那么以上的正則表達(dá)式應(yīng)該如何理解呢?首先,我們需要確定的是\s以及\S代表什么意思,*在正則表達(dá)式中需要轉(zhuǎn)義,所以加了\,這個(gè)正則表達(dá)式的意思就是匹配***//code***這樣的結(jié)構(gòu)。

          以上的源碼可以查看此處:https://github.com/eveningwater/my-web-projects/blob/master/react/5/src/utils/marked.ts。

          其它文本的定義

          還有2處的文本的定義,也就是問題選項(xiàng)的統(tǒng)計(jì)以及用戶回答問題的統(tǒng)計(jì),所以我們分別定義了2個(gè)函數(shù)來表示,如下:

          export function getCurrentQuestion(lang="en",order= 1,total = questions.length){
              return lang === 'en' ? `Question ${ order } of ${ total }` : `第${ order }題,共${ total }題`;
          }
          export function getCurrentAnswers(lang = "en",correctNum = 0,total= questions.length){
              return lang === 'en' ? `You got ${ correctNum } out of ${ total } correct!` : `共 ${ total }道題,您答對了 ${ correctNum } 道題!`;
          }

          這2個(gè)工具函數(shù)接受3個(gè)參數(shù),第一個(gè)參數(shù)代表語言類型,默認(rèn)值是"en"也就是英文模式,第二個(gè)代表當(dāng)前第幾題/正確題數(shù),第三個(gè)參數(shù)代表題的總數(shù)。然后根據(jù)這幾個(gè)參數(shù)返回一段文本,這個(gè)也沒什么好說的。

          實(shí)現(xiàn)思路分析

          初始化項(xiàng)目

          此處略過。可以參考文檔:https://reactjs.org/docs/create-a-new-react-app.html。

          基礎(chǔ)組件的實(shí)現(xiàn)

          接下來,我們實(shí)際上可以將頁面分成三大部分,第一部分即首頁,第二部分即問題選項(xiàng)頁,第三部分則是問題解析頁面,在解析頁面由于解析內(nèi)容過多,所以我們需要一個(gè)回到頂部的效果。在提及這三個(gè)部分的實(shí)現(xiàn)之前,我們首先需要封裝一些公共的組件,讓我們來一起看一下吧!

          中英文選項(xiàng)卡切換組件

          不管是首頁也好,問題頁也罷,我們都會(huì)看到右上角有一個(gè)中英文切換的選項(xiàng)卡組件,效果自不比多說,讓我們來思考一下應(yīng)該如何實(shí)現(xiàn)。首先思考一下DOM結(jié)構(gòu)。我們可以很快就想到結(jié)構(gòu)如下:

          <div class="tab-container">
              <div class="tab-item">en</div>
              <div class="tab-item">zh</div>
          </div>

          在這里,我們應(yīng)該知道類名應(yīng)該會(huì)是動(dòng)態(tài)操作的,因?yàn)樾枰砑右粋€(gè)選中效果,暫定類名為active,我在這里使用的是事件代理,將事件代理到父元素tab-container上。并且它的文本也是動(dòng)態(tài)的,因?yàn)樾枰獏^(qū)分中英文。于是我們可以很快寫出如下的代碼:

          import React from "react";
          import { parseObject } from '../data/data';
          import "../style/lang.css";
          export default class LangComponent extends React.Component {
              constructor(props){
                  super(props);
                  this.state = {
                      activeIndex:0
                  };
              }
              onTabHandler(e){
                  const { nativeEvent } = e;
                  const { classList } = nativeEvent.target;
                  if(classList.contains('tab-item') && !classList.contains('tab-active')){
                      const { activeIndex } = this.state;
                      let newActiveIndex = activeIndex === 0 ? 1 : 0;
                      this.setState({
                          activeIndex:newActiveIndex
                      });
                      this.props.changeLang(newActiveIndex);
                  }
              }
              render(){
                  const { lang } = this.props;
                  const { activeIndex } = this.state;
                  return (
                      <div className="tab-container" onClick = { this.onTabHandler.bind(this) }>
                          {
                              parseObject[lang]["tabs"].map(
                                  (tab,index) => 
                                  (
                                      <div className={`tab-item ${ activeIndex === index ? 'tab-active: ''}`}  key={tab}>{ tab }</div>
                                  )
                              )
                          }
                      </div>

                  )
              }
          }

          css樣式代碼如下:

          .tab-container {
              display: flex;
              align-items: center;
              justify-content: center;
              border:1px solid #f2f3f4;
              border-radius5px;
              position: fixed;
              top15px;
              right15px;
          }
          .tab-container > .tab-item {
              padding8px 15px;
              color#e7eaec;
              cursor: pointer;
              backgroundlinear-gradient(to right,#515152,#f3f3f7);
              transition: all .3s cubic-bezier(0.1750.8850.321.275);
          }
          .tab-container > .tab-item:first-child {
              border-top-left-radius5px;
              border-bottom-left-radius:5px;
          }
          .tab-container > .tab-item:last-child {
              border-top-right-radius5px;
              border-bottom-right-radius:5px;
          }
          .tab-container > .tab-item.tab-active,.tab-container > .tab-item:hover {
              color#fff;
              backgroundlinear-gradient(to right,#53b6e7,#0c6bc9);
          }

          js邏輯,我們可以看到我們通過父組件傳遞一個(gè)lang參數(shù)用來確定中英文模式,然后開始訪問定義數(shù)據(jù)上的tabs,即數(shù)組,react.js渲染列表通常都是使用map方法。事件代理,我們可以看到我們是通過獲取原生事件對象nativeEvent拿到類名,判斷元素是否含有tab-item類名,從而確定點(diǎn)擊的是子元素,然后調(diào)用this.setState更改當(dāng)前的索引項(xiàng),用來確定當(dāng)前是哪項(xiàng)被選中。由于只有兩項(xiàng),所以我們可以確定當(dāng)前索引項(xiàng)不是0就是1,并且我們也暴露了一個(gè)事件changeLang給父元素以便父元素可以實(shí)時(shí)的知道語言模式的值。

          至于樣式,都是比較基礎(chǔ)的樣式,沒有什么好說的,需要注意的就是我們是使用固定定位將選項(xiàng)卡組件固定在右上角的。以上的源碼可以查看此處。

          接下來,我們來看第二個(gè)組件的實(shí)現(xiàn)。

          底部內(nèi)容組件

          底部內(nèi)容組件比較簡單,就是一個(gè)標(biāo)簽包裹內(nèi)容。代碼如下:

          import React from "react";
          import "../style/bottom.css";
          const BottomComponent = (props) => {
              return (
                  <div className="bottom" id="bottom">{ props.children }</div>
              )
          }
          export default BottomComponent;

          CSS代碼如下:

          .bottom {
              position: fixed;
              bottom5px;
              left50%;
              transformtranslateX(-50%);
              color#fff;
              font-size18px;
          }

          也就是函數(shù)組件的寫法,采用固定定位定位在底部。以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          內(nèi)容組件的實(shí)現(xiàn)

          該組件的實(shí)現(xiàn)也比較簡單,就是用p標(biāo)簽包裝了一下。如下:

          import React from "react";
          import "../style/content.css";
          const ContentComponent = (props) => {
              return (
                  <p className="content">{ props.children }</p>
              )
          }
          export default ContentComponent;

          CSS樣式代碼如下:

          .content {
              max-width35rem;
              width100%;
              line-height1.8;
              text-align: center;
              font-size18px;
              color#fff;
          }

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          渲染HTML字符串的組件

          這個(gè)組件其實(shí)也就是利用了react.jsdangerouslySetInnerHTML屬性來渲染html字符串的。代碼如下:

          import "../style/render.css";
          export function createMarkup(template{
            return { __html: template };
          }
          const RenderHTMLComponent = (props) => {
              const { template } = props;
              let renderTemplate = typeof template === 'string' ? template : "";
              return <div dangerouslySetInnerHTML={createMarkup( renderTemplate )} className="render-content"></div>;
          }
          export default RenderHTMLComponent;

          CSS樣式代碼如下:

          .render-content a,.render-content{
              color#fff;
          }
          .render-content a {
              border-bottom:1px solid #fff;
              text-decoration: none;
          }
          .render-content code {
              color#245cd4;
              background-color#e5e2e2;
              border-radius5px;
              font-size16px;
              display: block;
              white-space: pre;
              padding15px;
              margin15px 0;
              word-break: break-all;
              overflow: auto;
          }
          .render-content a:hover {
              color:#efa823;
              border-color#efa823;
          }

          如代碼所示,我們可以看到其實(shí)我們就是dangerouslySetInnerHTML屬性綁定一個(gè)函數(shù),將模板字符串當(dāng)做參數(shù)傳入這個(gè)函數(shù)組件,在函數(shù)組件當(dāng)中,我們返回一個(gè)對象,結(jié)構(gòu)即:{ __html:template }。其它也就沒有什么好說的。

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          標(biāo)題組件的實(shí)現(xiàn)

          標(biāo)題組件也就是對h1~h6標(biāo)簽的一個(gè)封裝,代碼如下:

          import React from "react";
          const TitleComponent = (props) => {
              let TagName = `h${ props.level || 1 }`;
              return (
                  <React.Fragment>
                      <TagName>{ props.children }</TagName>
                  </React.Fragment>

              )
          }
          export default TitleComponent;

          整體邏輯也不復(fù)雜,就是根據(jù)父元素傳入的一個(gè)level屬性從而確定是h1 ~ h6的哪個(gè)標(biāo)簽,也就是動(dòng)態(tài)組件的寫法。在這里,我們使用了Fragment來包裹了一下組件,關(guān)于Fragment組件的用法可以參考文檔。我的理解,它就是一個(gè)占位標(biāo)簽,由于react.js虛擬DOM的限制需要提供一個(gè)根節(jié)點(diǎn),所以這個(gè)占位標(biāo)簽的出現(xiàn)就是為了解決這個(gè)問題。當(dāng)然,如果是typescript,我們還需要顯示的定義一個(gè)類型,如下:

          import React, { FunctionComponent,ReactNode }from "react";
          interface propType {
              level:number,
              children?:ReactNode
          }
          //這一行代碼是需要的
          type HeadingTag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
          const TitleComponent:FunctionComponent<propType> = (props:propType) => {
              //這里斷言一下只能是h1~h6的標(biāo)簽名
              let TagName = `h${ props.level }` as HeadingTag;
              return (
                  <React.Fragment>
                      <TagName>{ props.children }</TagName>
                  </React.Fragment>
              )
          }
          export default TitleComponent;

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          按鈕組件的實(shí)現(xiàn)

          按鈕組件是一個(gè)最基本的組件,它的默認(rèn)樣式肯定是不符合我們的需求的,所以我們需要將它簡單的封裝一下。如下所示:

          import React from "react";
          import "../style/button.css";
          export default class ButtonComponent extends React.Component {
              constructor(props){
                  super(props);
                  this.state = {
                      typeArr:["primary","default","danger","success","info"],
                      sizeArr:["mini",'default',"medium","normal","small"]
                  }
              }
              onClickHandler(){
                  this.props.onClick && this.props.onClick();
              }
              render(){
                  const { nativeType,type,long,size,className,forwardedRef } = this.props;
                  const { typeArr,sizeArr } = this.state;
                  const buttonType = type && typeArr.indexOf(type) > -1 ? type : 'default';
                  const buttonSize = size && sizeArr.indexOf(size) > -1 ? size : 'default';
                  let longClassName = '';
                  let parentClassName = '';
                  if(className){
                      parentClassName = className;
                  }
                  if(long){
                      longClassName = "long-btn";
                  }
                  return (
                      <button
                          ref={forwardedRef}
                          type={nativeType} 
                          className={ `btn btn-${ buttonType } ${ longClassName } btn-size-${buttonSize} ${parentClassName}`} 
                          onClick={ this.onClickHandler.bind(this)}
                      >
          { this.props.children }</button>

                  )
              }
          }

          CSS樣式代碼如下:

          .btn {
              padding14px 18px;
              outline: none;
              display: inline-block;
              border1px solid var(--btn-default-border-color);
              colorvar(--btn-default-font-color);
              border-radius8px;
              background-colorvar(--btn-default-color);
              font-size14px;
              letter-spacing2px;
              cursor: pointer;
          }
          .btn.btn-size-default {
              padding14px 18px;
          }
          .btn.btn-size-mini {
              padding6px 8px;
          }
          .btn:not(.btn-no-hover):hover,.btn:not(.btn-no-active):active,.btn.btn-active {
              border-colorvar(--btn-default-hover-border-color);
              background-colorvar(--btn-default-hover-color);
              color:var(--btn-default-hover-font-color);
          }
          .btn.long-btn {
              width100%;
          }

          這里對按鈕的封裝,主要是將按鈕分類,通過疊加類名的方式,給按鈕加各種類名,從而達(dá)到不同類型的按鈕的實(shí)現(xiàn)。然后暴露一個(gè)onClick事件。關(guān)于樣式代碼,這里是通過CSS變量的方式。代碼如下:

          :root {
              --btn-default-color:transparent;
              --btn-default-border-color:#d8dbdd;
              --btn-default-font-color:#ffffff;
              --btn-default-hover-color:#fff;
              --btn-default-hover-border-color:#a19f9f;
              --btn-default-hover-font-color:#535455;
              /* 1 */
              --bg-first-radial-first-color:rgba(5041570.271);
              --bg-first-radial-second-color:rgba(7,58,255,0);
              --bg-first-radial-third-color:rgba(17195201,1);
              --bg-first-radial-fourth-color:rgba(220,78,78,0);
              --bg-first-radial-fifth-color:#09a5ed;
              --bg-first-radial-sixth-color:rgba(255,0,0,0);
              --bg-first-radial-seventh-color:#3d06a3;
              --bg-first-radial-eighth-color:#7eb4e6;
              --bg-first-radial-ninth-color:#4407ed;
              /* 2 */
              --bg-second-radial-first-color:rgba(5041570.41);
              --bg-second-radial-second-color:rgba(7,58,255,0.1);
              --bg-second-radial-third-color:rgba(1751201,1);
              --bg-second-radial-fourth-color:rgba(220,78,78,0.2);
              --bg-second-radial-fifth-color:#090ded;
              --bg-second-radial-sixth-color:rgba(255,0,0,0.1);
              --bg-second-radial-seventh-color:#0691a3;
              --bg-second-radial-eighth-color:#807ee6;
              --bg-second-radial-ninth-color:#07ede1;
              /* 3 */
              --bg-third-radial-first-color:rgba(5041570.111);
              --bg-third-radial-second-color:rgba(7,58,255,0.21);
              --bg-third-radial-third-color:rgba(118172011);
              --bg-third-radial-fourth-color:rgba(220,78,78,0.2);
              --bg-third-radial-fifth-color:#2009ed;
              --bg-third-radial-sixth-color:rgba(255,0,0,0.3);
              --bg-third-radial-seventh-color:#0610a3;
              --bg-third-radial-eighth-color:#c07ee6;
              --bg-third-radial-ninth-color:#9107ed;
              /* 4 */
              --bg-fourth-radial-first-color:rgba(5041570.171);
              --bg-fourth-radial-second-color:rgba(7,58,255,0.2);
              --bg-fourth-radial-third-color:rgba(164172011);
              --bg-fourth-radial-fourth-color:rgba(220,78,78,0.1);
              --bg-fourth-radial-fifth-color:#09deed;
              --bg-fourth-radial-sixth-color:rgba(255,0,0,0);
              --bg-fourth-radial-seventh-color:#7106a3;
              --bg-fourth-radial-eighth-color:#7eb4e6;
              --bg-fourth-radial-ninth-color:#ac07ed;
          }

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          注意:這里的按鈕組件樣式事實(shí)上還沒有寫完,其它類型的樣式因?yàn)槲覀円獙?shí)現(xiàn)的網(wǎng)站沒有用到所以沒有去實(shí)現(xiàn)。

          問題選項(xiàng)組件

          實(shí)際上就是問題部分頁面的實(shí)現(xiàn),我們先來看實(shí)際的代碼:

          import React from "react";
          import { QuestionArray } from "../data/data";
          import ButtonComponent from './buttonComponent';
          import TitleComponent from './titleComponent';
          import "../style/quiz-wrapper.css";
          export default class QuizWrapperComponent extends React.Component {
              constructor(props:PropType){
                  super(props);
                  this.state = {
                      
                  }
              }
              onSelectHandler(select){
                  this.props.onSelect && this.props.onSelect(select);
              }
              render(){
                  const { question } = this.props;
                  return (
                      <div className="quiz-wrapper flex-center flex-direction-column">
                          <TitleComponent level={1}>{ question.question }</TitleComponent>
                          <div className="button-wrapper flex-center flex-direction-column">
                              {
                                  question.answer.map((select,index) => (
                                      <ButtonComponent 
                                          nativeType="button" 
                                          onClick={ this.onSelectHandler.bind(this,select)}
                                          className="mt-10 btn-no-hover btn-no-active"
                                          key={select}
                                          long
                                      >
          { select }</ButtonComponent>
                                  ))
                              }
                          </div>
                      </div>

                  )
              }
          }

          css樣式代碼如下:

          .quiz-wrapper {
              width100%;
              height100vh;
              padding1rem;
              max-width600px;
          }
          .App {
            height100vh;
            overflow:hidden;
          }
          .App h1 {
            color#fff;
            font-size32px;
            letter-spacing2px;
            margin-bottom15px;
            text-align: center;
          }
          .App .button-wrapper {
            max-width25rem;
            width100%;
            display: flex;
          }
          * {
            margin0;
            padding0;
            box-sizing: border-box;
          }
          body {
            height:100vh;
            overflow: hidden;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI''Roboto''Oxygen',
              'Ubuntu''Cantarell''Fira Sans''Droid Sans''Helvetica Neue',
              sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            background-imageradial-gradient(49% 81% at 45% 47%, var(--bg-first-radial-first-color) 0,var(--bg-first-radial-second-color) 100%),
                              radial-gradient(113% 91% at 17% -2%,var(--bg-first-radial-third-color) 1%,var(--bg-first-radial-fourth-color) 99%),
                              radial-gradient(142% 91% at 83% 7%,var(--bg-first-radial-fifth-color) 1%,var(--bg-first-radial-sixth-color) 99%),
                              radial-gradient(142% 91% at -6% 74%,var(--bg-first-radial-seventh-color) 1%,var(--bg-first-radial-sixth-color) 99%),
                              radial-gradient(142% 91% at 111% 84%,var(--bg-first-radial-eighth-color) 0,var(--bg-first-radial-ninth-color) 100%);
            animation:background 50s linear infinite;
          }

          code {
            font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
              monospace;
          }
          .mt-10 {
            margin-top10px;
          }
          .ml-5 {
            margin-left5px;
          }
          .text-align {
            text-align: center;
          }
          .flex-center {
            display: flex;
            justify-content: center;
            align-items: center;
          }
          .flex-direction-column {
            flex-direction: column;
          }
          .w-100p {
            width100%;
          }
          ::-webkit-scrollbar {
            width5px;
            height10px;
            backgroundlinear-gradient(45deg,#e9bf89,#c9a120,#c0710a);
          }
          ::-webkit-scrollbar-thumb {
             width5px;
             height5px;
             backgroundlinear-gradient(180deg,#d33606,#da5d4d,#f0c8b8);
          }
          @keyframes background {
              0% {
                background-imageradial-gradient(49% 81% at 45% 47%, var(--bg-first-radial-first-color) 0,var(--bg-first-radial-second-color) 100%),
                                  radial-gradient(113% 91% at 17% -2%,var(--bg-first-radial-third-color) 1%,var(--bg-first-radial-fourth-color) 99%),
                                  radial-gradient(142% 91% at 83% 7%,var(--bg-first-radial-fifth-color) 1%,var(--bg-first-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at -6% 74%,var(--bg-first-radial-seventh-color) 1%,var(--bg-first-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at 111% 84%,var(--bg-first-radial-eighth-color) 0,var(--bg-first-radial-ninth-color) 100%);
              }
              25%,50% {
                background-imageradial-gradient(49% 81% at 45% 47%, var(--bg-second-radial-first-color) 0,var(--bg-second-radial-second-color) 100%),
                                  radial-gradient(113% 91% at 17% -2%,var(--bg-second-radial-third-color) 1%,var(--bg-second-radial-fourth-color) 99%),
                                  radial-gradient(142% 91% at 83% 7%,var(--bg-second-radial-fifth-color) 1%,var(--bg-second-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at -6% 74%,var(--bg-second-radial-seventh-color) 1%,var(--bg-second-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at 111% 84%,var(--bg-second-radial-eighth-color) 0,var(--bg-second-radial-ninth-color) 100%);
              }
              50%,75% {
                background-imageradial-gradient(49% 81% at 45% 47%, var(--bg-third-radial-first-color) 0,var(--bg-third-radial-second-color) 100%),
                                  radial-gradient(113% 91% at 17% -2%,var(--bg-third-radial-third-color) 1%,var(--bg-third-radial-fourth-color) 99%),
                                  radial-gradient(142% 91% at 83% 7%,var(--bg-third-radial-fifth-color) 1%,var(--bg-third-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at -6% 74%,var(--bg-third-radial-seventh-color) 1%,var(--bg-third-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at 111% 84%,var(--bg-third-radial-eighth-color) 0,var(--bg-third-radial-ninth-color) 100%);
              }
              100% {
                background-imageradial-gradient(49% 81% at 45% 47%, var(--bg-fourth-radial-first-color) 0,var(--bg-fourth-radial-second-color) 100%),
                                  radial-gradient(113% 91% at 17% -2%,var(--bg-fourth-radial-third-color) 1%,var(--bg-fourth-radial-fourth-color) 99%),
                                  radial-gradient(142% 91% at 83% 7%,var(--bg-fourth-radial-fifth-color) 1%,var(--bg-fourth-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at -6% 74%,var(--bg-fourth-radial-seventh-color) 1%,var(--bg-fourth-radial-sixth-color) 99%),
                                  radial-gradient(142% 91% at 111% 84%,var(--bg-fourth-radial-eighth-color) 0,var(--bg-fourth-radial-ninth-color) 100%);
              }
          }

          可以看到,我們使用h1標(biāo)簽來顯示問題,四個(gè)選項(xiàng)都使用的按鈕標(biāo)簽,我們將按鈕標(biāo)簽選中的是哪一項(xiàng),通過暴露一個(gè)事件onSelect給傳遞出去。通過使用該組件的時(shí)候傳遞question數(shù)據(jù)就可以確定一組問題以及選項(xiàng)答案。所以實(shí)現(xiàn)效果如下圖所示:

          img

          這個(gè)組件里面可能比較復(fù)雜一點(diǎn)的是CSS布局,有采用彈性盒子布局以及背景色漸變動(dòng)畫等等,其它的也沒什么好說的。

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          解析組件

          解析組件實(shí)際上就是解析頁面部分的一個(gè)封裝。我們先來看一下實(shí)現(xiàn)效果:

          img

          根據(jù)上圖,我們可以得知解析組件分為六大部分。第一部分首先是對用戶回答所作的一個(gè)正確統(tǒng)計(jì),實(shí)際上就是一個(gè)標(biāo)題組件,第二部分則同樣也是一個(gè)標(biāo)題組件,也就是題目信息。第三部分則是正確答案,第四部分則是用戶的回答,第五部分則是確定用戶回答是正確還是錯(cuò)誤,第六部分就是實(shí)際的解析。

          我們來看一下實(shí)現(xiàn)代碼:

          import React from "react";
          import { parseObject,questions } from "../data/data";
          import { marked } from "../utils/marked";
          import RenderHTMLComponent from './renderHTML';
          import "../style/parse.css";
          export default class ParseComponent extends React.Component {
              constructor(props){
                  super(props);
                  this.state = {};
              }
              render(){
                  const { lang,userAnswers } = this.props;
                  const setTypeClassName = (index) => 
                  `answered-${ questions[index].correct === userAnswers[index] ? 'correctly' : 'incorrectly'}`;
                  return (
                      <ul className="result-list">
                          {
                              parseObject[lang].detail.map((content,index) => (
                                  <li 
                                      className={`result-item ${ setTypeClassName(index) }`} key={content}>

                                      <span className="result-question">
                                          <span className="order">{(index + 1)}.</span>
                                          { questions[index].question }
                                      </span>
                                      <div className="result-item-wrapper">
                                          <span className="result-correct-answer">
                                              { parseObject[lang].output }:<span className="ml-5 result-correct-answer-value">{ questions[index].correct }</span>
                                          </span>
                                          <span className="result-user-answer">
                                              {parseObject[lang].answer }:<span className="ml-5 result-user-answer-value">{userAnswers[index]}</span>
                                          </span>
                                          <span 
                                              className={`inline-answer ${ setTypeClassName(index) }`}>

                                              {
                                                  questions[index].correct === userAnswers[index] 
                                                  ? parseObject[lang].successMsg 
                                                  : parseObject[lang].errorMsg
                                              }
                                          </span>
                                          <RenderHTMLComponent template={ marked(content) }></RenderHTMLComponent>
                                      </div>
                                  </li>
                              ))
                          }
                      </ul>

                  )
              }
          }

          CSS樣式代碼如下:

          .result-wrapper {
            width100%;
            height100%;
            padding60px 15px 40px;
            overflow-x: hidden;
            overflow-y: auto;
          }
          .result-wrapper .result-list {
            list-style: none;
            padding-left0;
            width100%;
            max-width600px;
          }
          .result-wrapper .result-list .result-item {
            background-color#020304;
            border-radius4px;
            margin-bottom2rem;
            color#fff;
          }
          .result-content .render-content {
            max-width600px;
            line-height1.5;
            font-size18px;
          }
          .result-wrapper .result-question {
              padding:25px;
              background-color#1b132b;
              font-size22px;
              letter-spacing2px;
              border-radius4px 4px 0 0;
          }
          .result-wrapper .result-question .order {
              margin-right8px;
          }
          .result-wrapper .result-item-wrapper,.result-wrapper .result-list .result-item {
              display: flex;
              flex-direction: column;
          }
          .result-wrapper .result-item-wrapper {
              padding25px;
          }
          .result-wrapper .result-item-wrapper .result-user-answer {
            letter-spacing1px;
          }
          .result-wrapper .result-item-wrapper .result-correct-answer .result-correct-answer-value,
          .result-wrapper .result-item-wrapper .result-user-answer .result-user-answer-value {
             font-weight: bold;
             font-size20px;
          }
          .result-wrapper .result-item-wrapper .inline-answer {
              padding:15px 25px;
              max-width250px;
              margin:1rem 0;
              border-radius5px;
          }
          .result-wrapper .result-item-wrapper .inline-answer.answered-incorrectly {
              background-color#d82323;
          }
          .result-wrapper .result-item-wrapper .inline-answer.answered-correctly {
              background-color#4ee24e;
          }

          可以看到根據(jù)我們前面分析的六大部分,我們已經(jīng)可以確定我們需要哪些組件,首先肯定是渲染一個(gè)列表,因?yàn)橛?0道題的解析,并且我們也知道根據(jù)傳遞的lang確定中英文模式。另外一個(gè)userAnswers則是用戶的回答,根據(jù)用戶的回答和正確答案做匹配,我們就可以知道用戶回答是正確還是錯(cuò)誤。這也就是如下這行代碼的意義:

          const setTypeClassName = (index) => `answered-${ questions[index].correct === userAnswers[index] ? 'correctly' : 'incorrectly'}`;

          就是通過索引,確定返回的是正確的類名還是錯(cuò)誤的類名,通過類名來添加樣式,從而確定用戶回答是否正確。我們將以上代碼拆分一下,就很好理解了。如下:

          1.題目信息

          <span className="result-question">
               <span className="order">{(index + 1)}.</span>
               { questions[index].question }
          </span>

          2.正確答案

           <span className="result-correct-answer">
              { parseObject[lang].output }:
              <span className="ml-5 result-correct-answer-value">{ questions[index].correct }</span>
          </span>

          3.用戶回答

          <span className="result-user-answer">
            {parseObject[lang].answer }:
            <span className="ml-5 result-user-answer-value">{userAnswers[index]}</span>
          </span>

          4.提示信息

          <span className={`inline-answer ${ setTypeClassName(index) }`}>
               {
                   questions[index].correct === userAnswers[index] 
                   ? parseObject[lang].successMsg 
                   : parseObject[lang].errorMsg
               }
          </span>

          5.答案解析

          答案解析實(shí)際上就是渲染HTML字符串,所以我們就可以通過使用之前封裝好的組件。

          <RenderHTMLComponent template={ marked(content) }></RenderHTMLComponent>

          這個(gè)組件完成之后,實(shí)際上,我們的整個(gè)項(xiàng)目的大部分就已經(jīng)完成了,接下來就是一些細(xì)節(jié)的處理。

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          讓我們繼續(xù),下一個(gè)組件的實(shí)現(xiàn)也是最難的,也就是回到頂部效果的實(shí)現(xiàn)。

          回到頂部按鈕組件

          回到頂部組件的實(shí)現(xiàn)思路其實(shí)很簡單,就是通過監(jiān)聽滾動(dòng)事件確定回到頂部按鈕的顯隱狀態(tài),當(dāng)點(diǎn)擊回到頂部按鈕的時(shí)候,我們需要通過定時(shí)器以一定增量來進(jìn)行計(jì)算scrollTop,從而達(dá)到平滑回到頂部的效果。請看代碼如下:

          import React, { useEffect } from "react";
          import ButtonComponent from "./buttonComponent";
          import "../style/top.css";
          const TopButtonComponent = React.forwardRef((props, ref) => {
              const svgRef = React.createRef();
              const setPathElementFill = (paths, color) => {
                if (paths) {
                  Array.from(paths).forEach((path) => path.setAttribute("fill", color));
                }
              };
              const onMouseEnterHandler = () => {
                const svgPaths = svgRef?.current?.children;
                if (svgPaths) {
                  setPathElementFill(svgPaths, "#2396ef");
                }
              };
              const onMouseLeaveHandler = () => {
                const svgPaths = svgRef?.current?.children;
                if (svgPaths) {
                  setPathElementFill(svgPaths, "#ffffff");
                }
              };
              const onTopHandler = () => {
                props.onClick && props.onClick();
              };
              return (
                <ButtonComponent
                  onClick={onTopHandler.bind(this)}
                  className="to-Top-btn btn-no-hover btn-no-active"
                  size="mini"
                  forwardedRef={ref}
                >

                  {props.children ? ( props.children) : (
                    <svg
                      className="icon"
                      viewBox="0 0 1024 1024"
                      version="1.1"
                      xmlns="http://www.w3.org/2000/svg"
                      p-id="4158"
                      onMouseEnter={onMouseEnterHandler.bind(this)}
                      onMouseLeave={onMouseLeaveHandler.bind(this)}
                      ref={svgRef}
                    >

                      <path
                        d="M508.214279 842.84615l34.71157 0c0 0 134.952598-188.651614 134.952598-390.030088 0-201.376427-102.047164-339.759147-118.283963-357.387643-12.227486-13.254885-51.380204-33.038464-51.380204-33.038464s-37.809117 14.878872-51.379181 33.038464C443.247638 113.586988 338.550111 251.439636 338.550111 452.816063c0 201.378473 134.952598 390.030088 134.952598 390.030088L508.214279 842.84615zM457.26591 164.188456l50.948369 0 50.949392 0c9.344832 0 16.916275 7.522324 16.916275 16.966417 0 9.377578-7.688099 16.966417-16.916275 16.966417l-50.949392 0-50.948369 0c-9.344832 0-16.917298-7.556093-16.917298-16.966417C440.347588 171.776272 448.036711 164.188456 457.26591 164.188456zM440.347588 333.852624c0-37.47859 30.387078-67.865667 67.865667-67.865667s67.865667 30.387078 67.865667 67.865667-30.387078 67.865667-67.865667 67.865667S440.347588 371.331213 440.347588 333.852624z"
                        p-id="4159"
                        fill={props.color}
                      ></path>

                      <path
                        d="M460.214055 859.812567c-1.87265 5.300726-2.90005 11.000542-2.90005 16.966417 0 12.623505 4.606925 24.189935 12.244882 33.103956l21.903869 37.510312c1.325182 8.052396 8.317433 14.216793 16.750499 14.216793 8.135284 0 14.929014-5.732561 16.585747-13.386892l0.398066 0 24.62177-42.117237c5.848195-8.284687 9.29469-18.425651 9.29469-29.325909 0-5.965875-1.027399-11.665691-2.90005-16.966417L460.214055 859.81359z"
                        p-id="4160"
                        fill={props.color}
                      ></path>

                      <path
                        d="M312.354496 646.604674c-18.358113 3.809769-28.697599 21.439288-23.246447 39.399335l54.610782 179.871647c3.114944 10.304693 10.918677 19.086707 20.529569 24.454972l8.036024-99.843986c1.193175-14.745842 11.432377-29.226648 24.737404-36.517705-16.502859-31.912827-34.381042-71.079872-49.375547-114.721835L312.354496 646.604674z"
                        p-id="4161"
                        fill={props.color}
                      ></path>

                      <path
                        d="M711.644481 646.604674l-35.290761-7.356548c-14.994506 43.641963-32.889061 82.810031-49.374524 114.721835 13.304004 7.291057 23.544229 21.770839 24.737404 36.517705l8.036024 99.843986c9.609869-5.368264 17.397229-14.150278 20.529569-24.454972L734.890928 686.004009C740.34208 668.043962 730.003618 650.414443 711.644481 646.604674z"
                        p-id="4162"
                        fill={props.color}
                      ></path>

                    </svg>
                  )}
                </ButtonComponent>

              );
            }
          );
          const TopComponent = (props) => {
            const btnRef = React.createRef();
            let scrollElement= null;
            let top_value = 0,timer = null;
            const updateTop = () => {
                  top_value -= 20;
                  scrollElement && (scrollElement.scrollTop = top_value);
                  if (top_value < 0) {
                      if (timer) clearTimeout(timer);
                      scrollElement && (scrollElement.scrollTop = 0);
                      btnRef.current && (btnRef.current.style.display = "none");
                  } else {
                      timer = setTimeout(updateTop, 1);
                  }
            };
            const topHandler = () => {
                  scrollElement = props.scrollElement?.current || document.body;
                  top_value = scrollElement.scrollTop;
                  updateTop();
                  props.onClick && props.onClick();
            };
            useEffect(() => {
              const scrollElement = props.scrollElement?.current || document.body;
              // listening the scroll event
              scrollElement && scrollElement.addEventListener("scroll", (e: Event) => {
                  const { scrollTop } = e.target;
                  if (btnRef.current) {
                    btnRef.current.style.display = scrollTop > 50 ? "block" : "none";
                  }
              });
            });
            return (<TopButtonComponent ref={btnRef} {...propsonClick={topHandler.bind(this)}></TopButtonComponent>);
          };
          export default TopComponent;

          CSS樣式代碼如下:

          .to-Top-btn {
              position: fixed;
              bottom15px;
              right15px;
              display: none;
              transition: all .4s ease-in-out;
          }
          .to-Top-btn .icon {
              width35px;
              height35px;
          }

          整個(gè)回到頂部按鈕組件分為了兩個(gè)部分,第一個(gè)部分我們是使用svg的圖標(biāo)作為回到頂部的點(diǎn)擊按鈕。首先是第一個(gè)組件TopButtonComponent,我們主要做了2個(gè)工作,第一個(gè)工作就是使用React.forwardRef API來將ref屬性進(jìn)行轉(zhuǎn)發(fā),或者說是將ref屬性用于通信。關(guān)于這個(gè)API的詳情可查看文檔 forwardRef API。然后就是通過ref屬性拿到svg標(biāo)簽下面的所有子元素,通過setAttribute方法來為svg標(biāo)簽添加懸浮改變字體色的功能。這就是以下這個(gè)函數(shù)的作用:

          const setPathElementFill = (paths, color) => {
             //將顏色值和path標(biāo)簽數(shù)組作為參數(shù)傳入,然后設(shè)置fill屬性值
             if (paths) {
               Array.from(paths).forEach((path) => path.setAttribute("fill", color));
             }
          };

          第二部分就是在鉤子函數(shù)useEffect中去監(jiān)聽元素的滾動(dòng)事件,從而確定回到頂部按鈕的顯隱狀態(tài)。并且封裝了一個(gè)更新scrollTop值的函數(shù)。

          const updateTop = () => {
              top_value -= 20;
              scrollElement && (scrollElement.scrollTop = top_value);
              if (top_value < 0) {
                  if (timer) clearTimeout(timer);
                  scrollElement && (scrollElement.scrollTop = 0);
                  btnRef.current && (btnRef.current.style.display = "none");
              } else {
                 timer = setTimeout(updateTop, 1);
              }
          };

          采用定時(shí)器來遞歸實(shí)現(xiàn)動(dòng)態(tài)更改scrollTop。其它也就沒有什么好說的呢。

          以上的源碼可以查看此處。讓我們看下一個(gè)組件的實(shí)現(xiàn)。

          app組件的實(shí)現(xiàn)

          實(shí)際上該組件就是將所有封裝的公共組件的一個(gè)拼湊。我們來看詳情代碼:

          import React, { useReducer, useState } from "react";
          import "../style/App.css";
          import LangComponent from "../components/langComponent";
          import TitleComponent from "../components/titleComponent";
          import ContentComponent from "../components/contentComponent";
          import ButtonComponent from "../components/buttonComponent";
          import BottomComponent from "../components/bottomComponent";
          import QuizWrapperComponent from "../components/quizWrapper";
          import ParseComponent from "../components/parseComponent";
          import RenderHTMLComponent from '../components/renderHTML';
          import TopComponent from '../components/topComponent';
          import { getCurrentQuestion, parseObject,questions,getCurrentAnswers,QuestionArray } from "../data/data";
          import { LangContext, lang } from "../store/lang";
          import { OrderReducer, initOrder } from "../store/count";
          import { marked } from "../utils/marked";
          import { computeSameAnswer } from "../utils/same";
          let collectionUsersAnswers [] = [];
          let collectionCorrectAnswers [] = questions.reduce((v,r) => {
            v.push(r.correct);
            return v;
          },[]);
          let correctNum = 0;
          function App() {
            const [langValue, setLangValue] = useState(lang);
            const [usersAnswers,setUsersAnswers] = useState(collectionUsersAnswers);
            const [correctTotal,setCorrectTotal] = useState(0);
            const [orderState,orderDispatch] = useReducer(OrderReducer,0,initOrder);
            const changeLangHandler = (index: number) => {
              const value = index === 0 ? "en" : "zh";
              setLangValue(value);
            };
            const startQuestionHandler = () => orderDispatch({ type:"reset",payload:1 });
            const endQuestionHandler = () => {
              orderDispatch({ type:"reset",payload:0 });
              correctNum = 0;
            };
            const onSelectHandler = (select:string) => {
              // console.log(select)
              orderDispatch({ type:"increment"});
              if(orderState.count > 25){
                  orderDispatch({ type:"reset",payload:25 });
              }
              if(select){
                collectionUsersAnswers.push(select);
              }
              correctNum = computeSameAnswer(correctNum,select,collectionCorrectAnswers,orderState.count);
              setCorrectTotal(correctNum);
              setUsersAnswers(collectionUsersAnswers);
            }
            const { count:order } = orderState;
            const wrapperRef = React.createRef();
            return (
              <div className="App flex-center">
                <LangContext.Provider value={langValue}>
                  <LangComponent lang={langValue} changeLang={changeLangHandler}></LangComponent>
                  {
                    order > 0 ? order <= 25 ? 
                      (
                          <div className="flex-center flex-direction-column w-100p">

                            <QuizWrapperComponent 
                                question={ questions[(order - 1 < 0 ? 0 : order - 1)] } 
                                onSelect={ onSelectHandler }
                              >

                              </QuizWrapperComponent>
                            <BottomComponent lang={langValue}>{getCurrentQuestion(langValue, order)}</BottomComponent>
                          </div>
                      ) 
                      : 
                      (
                        <div className="w-100p result-wrapper" ref={wrapperRef}>
                           <div className="flex-center flex-direction-column result-content">
                              <TitleComponent level={1}>{ getCurrentAnswers(langValue,correctTotal)}</TitleComponent>
                              <ParseComponent lang={langValue} userAnswers={ usersAnswers }></ParseComponent>
                              <RenderHTMLComponent template={marked(parseObject[langValue].endContent)}></RenderHTMLComponent>
                              <div className="button-wrapper mt-10">
                                <ButtonComponent nativeType="button" long onClick={endQuestionHandler}>
                                  {parseObject[langValue].endBtn}
                                </ButtonComponent>
                              </div>
                           </div>
                           <TopComponent scrollElement={wrapperRef} color="#ffffff"></TopComponent>
                        </div>
                      )
                      : 
                      (
                        <div className="flex-center flex-direction-column">
                          <TitleComponent level={1}>{parseObject[langValue].title}</TitleComponent>
                          <ContentComponent>{parseObject[langValue].startContent}</ContentComponent>
                          <div className="button-wrapper mt-10">
                            <ButtonComponent nativeType="button" long onClick={startQuestionHandler}>
                              {parseObject[langValue].startBtn}
                            </ButtonComponent>
                          </div>
                        </div>
                      )
                  }
                </LangContext.Provider>
              </div>

            );
          }
          export default App;

          以上代碼涉及到了一個(gè)工具函數(shù),如下所示:

          export function computeSameAnswer(correct = 0,userAnswer,correctAnswers,index{
              if(userAnswer === correctAnswers[index - 1] && correct <= 25){
                  correct++;
              }
              return correct;
          }

          可以看到,這個(gè)函數(shù)的作用就是計(jì)算用戶回答的正確數(shù)的。

          另外,我們通過使用context.provider來將lang這個(gè)值傳遞給每一個(gè)組件,所以我們首先是需要?jiǎng)?chuàng)建一個(gè)context如下所示:

          import { createContext } from "react";
          export let lang = "en";
          export const LangContext = createContext(lang);

          代碼也非常簡單,就是調(diào)用React.createContext API來創(chuàng)建一個(gè)上下文,更多關(guān)于這個(gè)API的描述可以查看文檔。

          除此之外,我們還封裝了一個(gè)reducer函數(shù),如下所示:

          export function initOrder(initialCount{
            return { count: initialCount };
          }
          export function OrderReducer(state, action{
            switch (action.type) {
              case "increment":
                return { count: state.count + 1 };
              case "decrement":
                return { count: state.count - 1 };
              case "reset":
                return initOrder(action.payload ? action.payload : 0);
              default:
                throw new Error();
            }
          }

          這也是react.js的一種數(shù)據(jù)通信模式,狀態(tài)與行為(或者說叫載荷),是的我們可以通過調(diào)用一個(gè)方法來修改數(shù)據(jù)。比如這一段代碼就是這么使用的:

          const startQuestionHandler = () => orderDispatch({ type:"reset",payload:1 });
            const endQuestionHandler = () => {
              orderDispatch({ type:"reset",payload:0 });
              correctNum = 0;
            };
            const onSelectHandler = (select:string) => {
              // console.log(select)
              orderDispatch({ type:"increment"});
              if(orderState.count > 25){
                  orderDispatch({ type:"reset",payload:25 });
              }
              if(select){
                collectionUsersAnswers.push(select);
              }
              correctNum = computeSameAnswer(correctNum,select,collectionCorrectAnswers,orderState.count);
              setCorrectTotal(correctNum);
              setUsersAnswers(collectionUsersAnswers);
            }

          然后就是我們通過一個(gè)狀態(tài)值或者說是數(shù)據(jù)值order值從而決定頁面是渲染哪一部分的頁面。order <= 0的時(shí)候則是渲染首頁,order > 0 && order <= 25的時(shí)候則是渲染問題選項(xiàng)頁面,order > 25則是渲染解析頁面。

          以上的源碼可以查看此處:https://github.com/eveningwater/my-web-projects/blob/master/react/5/src/views/App.tsx。

          關(guān)于這個(gè)網(wǎng)站,我用vue3.X也實(shí)現(xiàn)了一遍,感興趣可以參考源碼:https://github.com/eveningwater/my-web-projects/tree/master/vue/21/。

          來自:夕水

          https://segmentfault.com/a/1190000040677455

          Node 社群


          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點(diǎn)贊在看” 支持一波??

          瀏覽 70
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美国产A片| 学生妹一级片 | 夜夜嗨AV一区二区三区 | 午夜人妻精品理论片中文字幕 | 中文字幕乱码中文乱码91 |