50天用react.js重寫50個web專案,我學到了什麼?

夕水發表於2021-10-17

1.Expanding Cards

效果如圖所示:

1.png

學到了什麼?
  1. React的函式元件中建立資料通訊,我們通常使用useState方法。它的使用方式採用陣列解構的方式,如下:
const [state,setState] = React.useState(false);//useState方法的引數可以是任意的JavaScript資料型別

解構的第一個引數是我們定義並且訪問的資料狀態,第二個引數則是當我們需要變動資料狀態時所呼叫的方法,其作用類似類元件中的this.setState

更詳細的使用方式參考文件 useState API

2.與類元件類似的鉤子函式,或者也可以理解為是函式元件的生命週期useEffect。它接收一個副作用函式effect作為引數,如下所示:

useEffect(() => {});//裡面的箭頭函式即副作用函式

以上示例只是做了一個簡單的更換文件標題,事實上在這個副作用函式中,我們可以做很多事情,詳情參考文件 useEffect API

3.React內部有自己的一套事件機制,我們稱之為合成事件。它與正常的JavaScript繫結事件很類似,但是它將事件命名方式採用了駝峰式寫法,並且它也對事件物件做了一層包裝,其名為SyntheticEvent。注意,這是一種跨瀏覽器端的包裝器,我們如果想要使用原生的事件物件,可以使用nativeEvent屬性,這在後面的示例中可能會涉及到。比如我們以上的事件繫結:

onClick={ onChangeHandler.bind(this,index) }

注意這裡,我們通常需要呼叫bind方法來將this繫結到該元件例項上,為什麼要使用bind方法來繫結this呢,這是因為繫結事件的回撥函式(如這裡的:onChangeHandler),它是作為一箇中間變數的,在呼叫該回撥函式的時候this指向會丟失,所以需要顯示的繫結this,這也是受JavaScript自身特性所限制的。詳情可參考React繫結this的原因中的解釋。與之類似的是在類元件中繫結合成事件,我們也一樣需要顯示的繫結this指向。

4.map方法的原理。map方法迭代一個陣列,然後根據返回值來對陣列項做處理,並返回處理後的陣列,請注意該方法不會改變原陣列。比如:

[1,2,3].map(i => i <= 1);//返回[1]

jsx中渲染列表也正是利用了map方法的這一特性,並且我們需要注意的是渲染列表的時候必須要指定一個key,這是為了方便DIFF演算法更好的比對虛擬DOM。

5.React中給標籤新增類名,我們是寫成className的,這是因為classJavaScript當做關鍵字。而如果我們需要動態繫結類名,可以看到,我們使用了模板字串,在這裡更像是寫JavaScript,比如我們可以利用三元表示式做判斷。

6.React中給標籤繫結style樣式,我們通常可以繫結一個物件,在React中,我們繫結動態資料就是寫一對{}花括號,然後style裡面的樣式我們通常宣告成物件來表示,比如:

{
    background:"#fff"
}

這代表它是一個樣式物件,然後React會在內部去將樣式物件轉換成樣式字串,然後新增到DOM的style物件中。

2.Progress Steps

效果如圖所示:

2.png

學到了什麼?

與第一個示例所用到的知識點很類似,相關的不必做說明。接下來我們來看不一樣的。

  1. 父元件向子元件傳遞資料,我們通常都是使用props。可以看到以上的示例,我們暴露了4個props,即:
width
stepItems
currentActive 
onClickItem

width就是設定步驟條的容器寬度,這個沒什麼可說的,stepItems就是步驟條的子元件,是一個陣列,也可以在陣列項中寫jsx。而currentActive則是傳入的當前是哪一步,是一個索引值,通常應該是數值。至於onClickItem則是子元件暴露給父元件的方法。

  1. 類元件的生命週期。在這個類元件當中,我們使用到了constructor,componentDidMount,render的生命週期鉤子函式。我們可以根據語義來推測,當一個類元件被初始化時所經歷的生命週期鉤子函式執行順序一定是constructor => render => componentDidMount。從語義上來將constructor是一個建構函式,用於初始化狀態,然後初始化完成之後,我們就會渲染元件,然後才是準備掛載元件。

額外的,我們擴充套件一下,根據文件說明,我們可以知道詳細的生命週期。React元件生命週期包含3個階段:

掛載 => 更新 => 解除安裝

在元件掛載之前執行的有:

constructor => static getDerivedStateFromProps => render => componentWillMount(即將過時) => componentDidMount

在元件state變更之後,也就是更新之後,執行的有:

static getDerivedStateFromProps => shouldComponentUpdate => render => getSnapshotBeforeUpdate => componentWillReceiveProps(即將過時) => componentWillUpdate(即將過時) => componentDidUpdate

在元件解除安裝之後,執行的有:

componentWillUnmount

另還有錯誤處理的生命週期,也就是在渲染過程,生命週期,或子元件的建構函式發生錯誤時,會執行的鉤子函式:

static getDerivedFromStateError => componentDidCatch

這裡面有些生命週期我們並沒有用到,有些則是幾乎涵蓋了我們後續的所有示例,所以我們一定要牢記元件的生命週期的順序。

但是就這個示例而言,我們學會的應該是如何封裝一個元件。

3.Rotating Navigation Animation

效果如圖所示:

3.png

學到了什麼?

一些相同同前面示例相同的知識點自不必說,我們來看不一樣的知識點。

1.模組組合匯出

//components目錄下的index.js檔案
export { default as Content } from './Content';
export { default as LeftMenu } from './LeftMenu';
export { default as NavList } from "./NavList";

可以看到,我們可以將元件如上面那樣匯出,然後我們就可以單獨引入一個js檔案,再引入相關的元件即可使用。如下:

import { Content, NavList, LeftMenu } from './components/index';

2.react元件如何渲染html字串

react提供了一個dangerouslySetInnerHTML屬性,這個屬性的屬性值是一個以__html作為屬性,值是html字串的物件,然後,我們就可以將html字串渲染成真實的DOM元素。如下所示:

<p dangerouslySetInnerHTML={{ __html:"<span>測試程式碼</span>" }}></p>
//<p><span>測試程式碼</span></p>

4.hidden-search-widget

效果如圖所示:

4.png

學到了什麼?

本示例同樣的與前面的示例有一樣的知識點,相關的不會做說明,只會做不同的知識點介紹,後續的同理。

1.判斷資料的型別。利用物件原型鏈上的toString方法,我們可以得到一個字串值,這個字串值的格式類似[object Object]這樣,也就是說我們可以通過這個字串值來判斷一個資料的型別。例如:

const getType = v => Object.prototype.toString.call(v).slice(8,-1).toLowerCase();
const isArray = v => getType(v) === "array";
isArray([1,2,3]);//true
isArray("");//false

這裡我們應該知道Object.prototype.toString.call(v)返回的就是類似[object Object]這樣的值。所以我們通過擷取開始索引為8,結束索引為該字串長度減1,也就是這裡的-1,我們就可以獲取到第二個值Object,然後再呼叫toLowerCase()方法來將全部字母轉換成小寫,然後,我們就可以知道資料的型別了。比如這裡的判斷是否是陣列,那麼只需要判斷該值是否等於"array"即可。

2.React.createRef API

有時候,我們恰恰需要操作一些原生DOM元素的API,例如這個示例的輸入框的關注焦點事件。這時候這個API就有了用武之地,我們相當於使用這個API建立一個與DOM元素通訊的橋樑,通過這個訪問這個API的例項的current屬性,我們就可以訪問到相應的DOM元素。

更詳細的可以參考文件createRef API

3.如何封裝一個input元件。

這個示例也教會了我們如何封裝一個input元件。

5.Blurry Loading

效果如圖所示:

5.png

學到了什麼?
  1. componentDidMount生命週期中建立定時器以及在componentWillUnmount中清除定時器。
  2. 類元件中的this.setState更新狀態。該方法接收2個引數,第一個引數則是我們的react狀態,它可以是一個函式,也可以是一個物件。第二個引數則是一個回撥函式。談到這裡,可能就會提到一個問題,那就是setState到底是非同步還是同步? 答案如下:

答:react中的setState在合成事件和鉤子函式中是"非同步"的,而在原生事件和setTimeout中則是同步的。

之所以是"非同步",並不代表在react內部中是"非同步"程式碼實現的,事實上,它仍然是同步執行的一個過程。

只是合成事件和鉤子函式的呼叫順序在更新之前,導致在合成函式和鉤子函式中沒法立即拿到更新後的值,所以就形成了所謂的"非同步"。

事實上,我們可以通過制定第二個引數即callback(回撥函式)來獲取到更新後的值。

react.js對setState的原始碼實現也不是很複雜,它將傳入的引數作為值新增到updater也就是更新器的一個定義好的佇列(即:enqueueSetState)中。

react中的批量更新優化也是建立在合成事件和鉤子函式(也就是"非同步")之上的,在原生事件和setTimeout中則不會進行批量更新。

比如在"非同步"中對同一個值進行多次setState,依據批量更新則會對其進行策略覆蓋,而如果是對不同的多個值setState,則會利用批量更新策略對其進行合併然後批量更新。

更詳細的可以參考文件 setState

除此之外,這裡也有一個非常重要的工具函式(這在js的實現中也提及過),如下所示:

const scale = (n,inMin,inMax,outerMin,outerMax) => (n - inMin) * (outerMax - outerMin) / (inMax - inMin) + outerMin;

這個工具函式的作用就是將一個範圍數字對映到另一個數字範圍。比方說,將1 ~ 100的數字範圍對映到0 ~ 1之間。
詳情

6.Scroll Animation

效果如圖所示:

6.png

學到了什麼?

1.動態元件

我們通過props傳遞一個值用來確定元件名。這裡以Title元件為例,如下所示:

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

雖然核心程式碼十分簡單,這裡需要注意,React元件需要首字母大寫,這算是一個約定的規則,其次我們通過props傳遞一個level用來確定我們使用的是h1~h6哪個來做標籤,事實上這裡我們應該對level做一個限制,只允許值為1~6

2.Fragment元素

這個元素類似於一個佔位符節點,我們知道,當兩個元素並列在一個React元件中,是不被允許的,React元件需要提供一個根節點,但有時候,我們不需要一個實際的元素作為根節點包裹它們,所以我們就可以使用Fragment元素來包裹它們。該元素還有一個簡寫<></>,事實上在Vue2.x中也有這個限制,這是受虛擬DOM DIFF演算法所限制的。

更詳細的可以見文件Fragment

3.函式防抖

export const throttle = (fn, time = 1000) => {
    let done = false;
    return (...args) => {
        if (!done) {
            fn.call(this, ...args);
            done = true;
        }
        setTimeout(() => {
            done = false;
        }, time)
    }
}

函式防抖與節流可參考 這篇文章

4.監聽滾動事件,事實上這裡的實現原理同JavaScript實現版本一致,只不過稍微轉換一下思維即可。

7. Split Landing Page

效果如圖所示:

7.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

8.Form Wave

效果如圖所示:

8.png

學到了什麼?
  1. setState更新物件,如果state是一個物件,我們有2種方式來更新。
    1.1 利用Object.assign方法來更新。
    1.2 直接覆蓋整個物件。

以上2種方式虛擬碼如下:

//  第1種方式
const loginInfo = Object.assign({},this.state.loginInfo,{
   [key]:updateValue
})
this.setState({
   loginInfo
});
// 第2種方式
let { loginInfo } = this.state;
loginInfo[key] = updateValue;
this.setState({ loginInfo });

9.Sound Board

效果如圖所示:

9.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

10. Dad Jokes

效果如圖所示:

10.png

學到了什麼?
  1. Suspense元件的使用。該元件可以指定一個載入指示器元件,用來實現元件的懶載入。更詳細的文件見 Suspense
  2. 介面請求通常都是在componentDidMount鉤子函式中完成的。由於不能直接在該鉤子函式中更改狀態(react.js會給出一個警告)。所以我們需要讓介面請求變成非同步。

11. Event Keycodes

效果如圖所示:

11.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

12. Faq Collapse

效果如圖所示:

12.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

13. Random Choice Picker

效果如圖所示:

13.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

14. Animated Navigation

效果如圖所示:

14.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

15. Incrementing Counter

效果如圖所示:

15.png

學到了什麼?
  1. react.js如何更新陣列的某一項?在這裡我是更新整個陣列的,或許這不是一種好的方式。也希望有大佬能提供思路。我的程式碼如下:
startCounter() {
    const counterList = [...this.state.counterList];
    // https://stackoverflow.com/questions/29537299/react-how-to-update-state-item1-in-state-using-setstate
    // https://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-react-js
    counterList.forEach(counter => {
      const updateCounter = () => {
          const value = counter.value;
          const increment = value / 100;
          if (counter.initValue < value) {
            counter.initValue = Math.ceil(counter.initValue + increment);
            // use setTimeout or setInterval ?
            counter.timer = setTimeout(updateCounter, 60);
          } else {
            counter.initValue = value;
            clearTimeout(counter.timer);
          }
          // update the array,maybe it's not a best way,is there any other way?
          this.setState({ counterList });
      }
      updateCounter();
    })
}

16. Drink Water

效果如圖所示:

16.png

學到了什麼?
  1. 這裡踩了一個坑,如果使用new Array().fill()來初始化狀態,會導致意想不到的渲染效果。所以這裡直接初始化了所有的陣列項。

詳見原始碼

17. movie-app

效果如圖所示:

17.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

PS:這個示例效果由於介面訪問受限,需要你懂的訪問。

18. background-slider

效果如圖所示:

18.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

19. theme-clock

效果如圖所示:

19.png

學到了什麼?
  1. 中英文切換是通過定義一個物件來完成的。其它的沒什麼好說的,都是前面提及過的知識點。
PS:本示例也用到了與示例5一樣的工具函式scale函式

20. button-ripple-effect

效果如圖所示:

20.png

學到了什麼?
  1. 可以通過呼叫函式來渲染一個元件。

21. drawing-app

效果如圖所示:

21.png

學到了什麼?
  1. 在react.js中使用ew-color-picker
  2. 這裡踩了一個坑,也就是說必須要設定線條的樣式。
this.ctx.lineCap = "round";

否則線條的樣式不對勁,雖然我也沒有搞清楚這裡面的原因。畢竟js版本的實現也沒有需要顯示的設定這個線條的樣式。

22. drag-n-drop

效果如圖所示:

22.png

學到了什麼?
  1. 這裡也踩了一個坑, 詳見原始碼註釋

23. content-placeholder

效果如圖所示:

23.png

學到了什麼?
  1. 一些判斷react元件的工具函式。如下:
import React from "react";
export function isString(value){
    return typeof value === "string";
}
export function isClassComponent(component) {
    return typeof component === 'function' && !!component.prototype.isReactComponent;
}

export function isFunctionComponent(component) {
    return typeof component === 'function' && String(component).indexOf('return React.createElement') > -1;
}

export function isReactComponent(component) {
    return isClassComponent(component) || isFunctionComponent(component);
}

export function isElement(element) {
    return React.isValidElement(element);
}

export function isDOMTypeElement(element) {
    return isElement(element) && typeof element.type === 'string';
}

export function isCompositeTypeElement(element) {
    return isElement(element) && typeof element.type === 'function';
}
  1. 如何封裝一個卡片元件。

24. kinetic-loader

效果如圖所示:

24.png

學到了什麼?

這個示例涉及到的知識點前面的示例都提及過,所以這裡不必贅述。

25. sticky-navbar

效果如圖所示:

25.png

學到了什麼?

這個示例涉及到的知識點除了一個工具函式,其它的知識點前面的示例都提及過,所以這裡不必贅述。

export function marked(template){
    return template.replace(/.+?[\s]/g,v => `<p>${v}</p>`);
}

這個工具函式的作用就是匹配以空格結束的任意字元,然後替換成p標籤包裹這些內容。

PS:這裡也做了移動端的佈局。

26. double-slider

效果如圖所示:

26.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

27. toast-notification

效果如圖所示:

27.png

學到了什麼?
  1. 可以利用ReactDOM.render來封裝一個函式呼叫的toast元件。
  2. ReactDOM.unmountComponentAtNode API的用法這個方法會將從DOM中解除安裝元件,包括事件處理器和state。詳見 文件
  3. getDerivedStateFromProps 靜態函式。詳見 文件

28. github-profiles

效果如圖所示:

28.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

29. double-click-heart

效果如圖所示:

29.png

學到了什麼?
  1. 從合成事件物件中讀取原生的事件物件。即nativeEvent屬性。

30. auto-text-effect

效果如圖所示:

30.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

31. password generator

效果如圖所示:

31.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

32. good-cheap-fast

效果如圖所示:

32.png

學到了什麼?
  1. 如何封裝一個switch元件,即開關小元件。

33. notes-app

效果如圖所示:

33.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

34. animated-countdown

效果如圖所示:

34.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

35. image-carousel

效果如圖所示:

35.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

36. hover board

效果如圖所示:

36.png

學到了什麼?
  1. react.js合成事件中的setState是同步的,所以這裡使用原生的監聽事件來實現。 詳見原始碼

37. pokedex

效果如圖所示:

37.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

38. mobile-tab-navigation

效果如圖所示:

38.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

39. password-strength-background

效果如圖所示:

39.png

學到了什麼?
  1. 照著 文件 一步步將tailwindcss新增到create-react-app腳手架中。

40. 3D-background-boxes

效果如圖所示:

40.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

41. Verify Your Account

效果如圖所示:

41.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

42. live-user-filter

效果如圖所示:

42.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

43. feedback-ui-design

效果如圖所示:

43.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

44. custom-range-slider

效果如圖所示:

44.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

45. netflix-mobile-navigation

效果如圖所示:

45.png

學到了什麼?
  1. 為如何實現一個遞迴元件提供了思路。

46. quiz-app

效果如圖所示:

46.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

47. testimonial-box-switcher

效果如圖所示:

47.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

48. random-image-feed

效果如圖所示:

48.png

學到了什麼?
  1. 實現一個簡易版本的預覽圖片。

49. todo-list

效果如圖所示:

49.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

50. insect-catch-game

效果如圖所示:

50.png

學到了什麼?

這個示例涉及到的知識點,前面的示例都提及過,所以這裡不必贅述。

特別說明:這個示例算是一個比較綜合的示例了。

相關文章