0%

前段時間練習了一些 React 的基礎,對於 JSX 以及一些 hook 的用法比較有概念,所以就以 Bruce 的 React 課程實作再來練習,樣式部分以 Tailwind 為主此外也搭配 TS 來撰寫。

1. Header

  • 製作 IGHeader 元件,因為到不同頁面都會有 header 存在,所以可以將它放在外面的共用元件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const IGHeader: React.FC = () => {
const go = useNavigate()
return (
<header className="sticky top-0 border-b-[1px] bg-white border-gray-300 ">
<div className="flex justify-between items-center h-[60px] px-2 lg:max-w-[1024px] lg:mx-auto lg:px-0">
<img
className="w-[100px] cursor-pointer"
src="/images/logo.svg"
/>
<div className="flex">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 mr-4 cursor-pointer"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
onClick={() => go('/')}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 mr-4 cursor-pointer"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
onClick={() => go('/following')}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
<div className=" h-6 w-6 text-white bg-gray-900 font-bold flex rounded-full justify-center items-center">
E
</div>
</div>
</div>
</header>
)
}

2. IGContainer

頁面佈局設定,除了 限時動態頁面會呈現外,在追蹤者頁面也會使用到

  • 使用 Tailwind 設定左右版面以及當螢幕不同尺寸的設定
  • 將樣式寫在(style.div 裏面),作為一個組件 export 出去

3. 限時動態列表(IGStory)

  • 因隸屬於 home 裡面的位置,所以可以將檔案,放在 home 資料夾中,屬於 home 中的組件
    //通常在一份專案中不用引入兩個 css 框架
    實作:
    1. List Container 的容器
      文件
    • 在組建內部要使用 JS 相關的邏輯的寫法,要在要面用大括號刮起來
    • 每一個小圖都是一個 item ,所以可以先製作組件

限時動態 item 組件

  • 此組件建立其中的小項目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react'

type ItemProps = {
name: string;
avatar: string;
}

const Item: React.FC<ItemProps> = ({ name, avatar }) => {
return (
<div className=" text-center ">
<div className=" rounded-full w-[56px] h-[56px] ring-2 border-2 border-white ring-red-500 mx-[11px] p-[3px] "
style={{
backgroundImage: `url(${avatar})`,
backgroundPosition: "center",
backgroundSize: "cover",
}}>
</div>
<p className="text-xs mt-1">{name}</p>
</div>
)
}

export default Item

外層

  • 在外層的主要檔案,就是將模板建立好,讓 item 可以 map 進去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const IGStory: React.FC = () => {
const storyData = [
{
"id": 1,
"name": "bruce_fe",
"avatar": "/images/avatars/a1.png"
},
{
"id": 2,
"name": "max",
"avatar": "/images/avatars/a2.png"
},
{
"id": 3,
"name": "fm",
"avatar": "/images/avatars/a3.png"
},
{
"id": 4,
"name": "joanne",
"avatar": "/images/avatars/a4.png"
},
{
"id": 5,
"name": "focus",
"avatar": "/images/avatars/a5.png"
},
{
"id": 6,
"name": "louis",
"avatar": "/images/avatars/a6.png"
},
{
"id": 7,
"name": "alvin",
"avatar": "/images/avatars/a7.png"
},
{
"id": 8,
"name": "grace",
"avatar": "/images/avatars/a8.png"
},
{
"id": 9,
"name": "rance",
"avatar": "/images/avatars/a9.png"
},
{
"id": 10,
"name": "bruce_fe",
"avatar": "/images/avatars/a10.png"
}
]
return (
<div className='w-full flex items-center h-[110px] box-border overflow-x-auto overflow-y-hidden no-scrollbar shadow-md lg:my-8'>
{/* <Item name="Mike" avatar='/images/avatars/a1.png' /> */}
{
storyData?.map((item) => {
const { id, name, avatar } = item
return <Item key={id} name={name} avatar={avatar} />
})
}
</div>
)
}

export default IGStory

3. User 組件

  • 使用者資訊表,在不同區塊都有重複出現,所以可以抽成共用組件
  • 現定義好 props 的型別
  • 依照藥的版型來切出樣貌,將變數帶入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type IGUserProps = {
size?: "medium" | "small";
showFollow?: boolean;
isFollowing?: boolean;
account?: string;
location?: string;
avatar?: string;
id?: number;
}

const IGUser: React.FC<IGUserProps> = ({
size = "small",
showFollow = false,
isFollowing = false,
account,
location,
avatar,
id,
}) => {
return (

<div className="flex h-[70px] items-center box-border px-4 ">
<div className={`${size === "small" ? "w-[40px] h-[40px]" : "w-[60px] h-[60px]"} overflow-hidden rounded-full `}
style={{
backgroundImage: `url(${avatar})`,
backgroundPosition: "center",
backgroundSize: "cover",
}}
></div>
<div className="ml-4">
<p className="text-sm font-bold">{account}</p>
<p className="text-gray-400">{location}</p>
</div>
{showFollow && (
<p
className={`${isFollowing ? "text-gray-700" : "text-blue-400"} text-xs ml-auto cursor-pointer font-bold`}
> {isFollowing ? "FOLLOWING" : "FOLLOW"}</p>
)

}
</div>
)
}
  • memo 對於重複渲染,效能優化使用

border-box

post 組件

  • 分為三個部分
  1. user
  • 可以使用前面所製作的 user 組件
  1. img
  • 直接放入該項圖片
  1. 評論區
  • 也可以製作成小組件,最後再放入 post 組件
  • 注意傳入 props 要先設定好傳入的型別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react'

type CommentsProps = {
likes: number;
description: string;
hashTags: string;
createTime: string;
account: string;
}

const Comments: React.FC<CommentsProps> = (
{
likes,
description,
hashTags,
createTime,
account,
}) => {
return (
<div className='px-4'>
<div className='flex items-center justify-between box-border my-4'>
<div className='flex py-1'>
// svg
</div>
<p className='text-sm font-bold'>{likes} likes</p>
<p className='text-sm font-bold'>{description} </p>
<p className='text-blue-400 font-bold'>{hashTags} </p>
<p className='text-gray-400 font-[500] text-xs mt-2'>View all 999 comments </p>
<p className="text-gray-400 text-[10px] mt-1">{createTime}</p>
</div>
)
}



profile 區塊

  • 實作快速,直接做出簡易的版面

  • 套用前面所做的 IGUser 即可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import IGUser from "components/IGUser"

const IGProfile = () => {
const followingList = [
{
id: 1,
location: "Singapore",
account: "max_999",
isFollowing: true,
avatar: "/images/avatars/a1.png",
},
{
id: 2,
location: "Singapore",
account: "fm_999",
isFollowing: true,
avatar: "/images/avatars/a2.png",
},
{
id: 3,
location: "Singapore",
account: "joanne_999",
isFollowing: true,
avatar: "/images/avatars/a3.png",
},
{
id: 4,
location: "Singapore",
account: "focus_999",
isFollowing: true,
avatar: "/images/avatars/a4.png",
},]

return (
<div className="mt-8 ml-6 border-box shadow-md px-4">
<div>
<IGUser size='medium' account='Tmommy_0814' avatar="/images/avatar.png" />
</div>
<p className="text-gray-400 px-4 mt-2">You are Following</p>
<div >
{
followingList?.map((item) => {
const { location, account, isFollowing, avatar, id } = item
return <IGUser location={location} account={account} isFollowing={isFollowing} avatar={avatar} key={id} showFollow />
})
}

</div>
</div>
)
}

export default IGProfile

小結

此次練習在使用 Tailwind 有些不習慣,主要是在於要稍微記得他的關鍵字並去文件查找屬性來應用,待使用到第三的區塊切版時就變得快速些~
另外,使用在課程中 Bruce 也會去說明每個區塊的切割以及重用性,可以將檔案分別放在哪個檔案,以利未來維護。
過去可能拿版面並未想太多,或是沒有多餘時間分析每一頁面的某些區塊是否有重複,我們可以依據建立開關或傳入的資料區紹維變動下版面,就可以達到一起使用 component 的效果。

Given an array nums of size n, return the majority element.

The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.

Example 1:

1
2
Input: nums = [3,2,3]
Output: 3

Example 2:

1
2
Input: nums = [2,2,1,1,1,2,2]
Output: 2

在一開始觀察題目,會直覺想說要找出陣列中出現最多次的元素,此外也注意到題目說明有提及運用 n/2 與陣列長度比較出現次數。
在一開始會傾向使用 雜湊表 來進行解題,如同找出陣列為一值的方式找出答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var majorityElement = function (nums) {
let newObj = {}
for (let num of nums) {
// console.log(num)
(!newObj[num]) ? newObj[num] = 1 : newObj[num]++
}
// console.log(newObj)
let nNum = nums.length / 2
// console.log('nNum', nNum)
for (let key in newObj) {
if (newObj[key] > nNum) return key
}

};

另外也發現可以使用,Boyer–Moore majority vote algorithm(摩爾投票算法)來解題。
宣告變數 result , 以及計算變數。
將陣列loop ,判斷陣列元素有無出現進行 count 的加減
majorityElement2([4, 5, 5, 4, 4]) 為範例:
第一個元素 4 ,因為 count === 0 , result === 4。
進入第二個元素 5,因為 result === 4 進入判斷式 else => count– 。 所以 count === 0 。
進入第三個元素 5,因為 result === 5 進入判斷式 result === elem => count++。 所以 count === 1 。
進入第四個元素 4,因為 result === 5 進入判斷式 else => count– 。 所以 count === 0 。
進入第五個元素 4,因為 result === 4 進入判斷式 result === elem => count++。 所以 count === 1 。

在迴圈的最後元素 4 ,為目前 count 為 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

var majorityElement2 = function (nums) {
let result;
let count = 0;

for (let elem of nums) {
if (count === 0) {
result = elem;
}
if (result === elem) {
count++;
} else {
count--;
}
}

return result;

};


  1. Majority Element II
    此題也是使用摩爾投票法進行解題

Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times.

Example 1:

1
2
Input: nums = [3,2,3]
Output: [3]

Example 2:

1
2
Input: nums = [1]
Output: [1]

Example 3:

1
2
Input: nums = [1,2]
Output: [1,2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var majorityElement = function (nums) {
// 只要是 > n/3 就進入陣列
let candidate1 = null;
let candidate2 = null;
let count1 = 0;
let count2 = 0;

for (let num of nums) {

if (candidate1 === num) {
count1++
} else if (candidate2 === num) {
count2++
} else if (count1 === 0) {
candidate1 = num;
count1++
} else if (count2 === 0) {
candidate2 = num;
count2++
} else {
count1--;
count2--;
}
}

console.log('data:', candidate1, candidate2)
count1 = 0;
count2 = 0;
const res = [];

limit = Math.floor(nums.length / 3)

for (let num of nums) {
if (candidate1 === num) count1++;
else if (candidate2 === num) {
count2++;
}
}
if (count1 > limit) res.push(candidate1)
if (count2 > limit) res.push(candidate2)
console.log(res, count1, count2)
return res

};
majorityElement([1, 2])

Given an array, rotate the array to the right by k steps, where k is non-negative.

Example 1:

1
2
3
4
5
6
Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]
1
2
3
4
5
6
7
Example 2:

Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation:
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]

1.由上述範例可以觀察到,根據 k 值進行抓取最後的元素至最前面,以此類推下去。
當 k 為某值的時後,會由陣列最後方取的該數目元素。
以下為直覺解法:

1
2
3
4
5
6
7
8
9
10
11
//會有 問題:Time Limit Exceeded
const rotateArray1 = function (nums, k) {
//unshift 將給入的元素值放到最前面
for (let i = 0; i < k; i++) {
console.log('nums.pop()', nums.pop())
nums.unshift(nums.pop());
}
console.log('num', nums)
return nums;
}

2.也可以先將陣列進行反轉 reverse ,再依據 k 值 來進行前後段區分,來進行反轉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const revNum = (nums, start, end) => {
while (start < end) {

[nums[start], nums[end]] = [nums[end], nums[start]]
console.log('[nums[start], nums[end]]', [nums[start], nums[end]])
start++;
end--;
}
console.log(nums)
}
// revNum([1, 2, 3, 4], 0, 3)

const rotateArray2 = function (nums, k) {
k = k % nums.length
//先整個陣列 reverse
nums.reverse()
revNum(nums, 0, k - 1)
revNum(nums, k, nums.length - 1)
}

想找專案練習來更認識測試的運用,至 youtube 找尋測試相關的教學。

建立 login 檔案

  • login.js : 製作登入元件

  • login.test.js : 撰寫測試

確認 輸入框 有渲染至文件

  • 使用 getByPlaceholderText 取得輸入框元素
1
2
3
4
5
6
7
8
9
10
11
12
<form>
<input
type="text"
placeholder="username"
value=''
/>
<input
type="password"
placeholder="password"
value=''
/>
</form>
1
2
3
4
5
6
7
8
9
10
11
test("username input should be rendered", () => {
render(<Login />);
const usernameInputEl = screen.getByPlaceholderText(/username/i);
expect(usernameInputEl).toBeInTheDocument();
});

test("password input should be rendered", () => {
render(<Login />);
const passwordInputEl = screen.getByPlaceholderText(/password/i);
expect(passwordInputEl).toBeInTheDocument();
});

確認畫面上有按鈕

  • getByRole 取得按鈕
1
2
3
4
5
test("button should be rendered", () => {
render(<Login />);
const buttonEl = screen.getByRole("button");
expect(buttonEl).toBeInTheDocument();
});

輸入框一開始為空

1
2
3
4
5
6
7
8
9
10
11
test("username input should be empty", () => {
render(<Login />);
const usernameInputEl = screen.getByPlaceholderText(/username/i);
expect(usernameInputEl.value).toBe("");
});

test("password input should be empty", () => {
render(<Login />);
const passwordInputEl = screen.getByPlaceholderText(/password/i);
expect(passwordInputEl.value).toBe("");
});

輸入框為空時,按鈕應為 disabled

  • 只要取得按鈕元素並使用 toBeDisabled()
1
2
3
4
5
test("button should be disabled", () => {
render(<Login />);
const buttonEl = screen.getByRole("button");
expect(buttonEl).toBeDisabled();
});

錯誤訊息的提示

  • 確保一開始進入畫面,該訊息不應該出現
  • 可以看見 toBeVisible() ,反之期望無法看見,就需加入not
1
2
3
4
5
test("error message should not be visible", () => {
render(<Login />);
const errorEl = screen.getByTestId("error");
expect(errorEl).not.toBeVisible();
});

輸入框的輸入事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test("username input should change", () => {
render(<Login />);
const usernameInputEl = screen.getByPlaceholderText(/username/i);
//模擬傳入的值
const testValue = "test";

fireEvent.change(usernameInputEl, { target: { value: testValue } });
expect(usernameInputEl.value).toBe(testValue);
});

test("password input should change", () => {
render(<Login />);
const passwordInputEl = screen.getByPlaceholderText(/password/i);
const testValue = "test";

fireEvent.change(passwordInputEl, { target: { value: testValue } });
expect(passwordInputEl.value).toBe(testValue);
});

當輸入框有值的時候,按鈕不應該 disabled

  • 期望按鈕可以點擊
  • 在 jsx 檔案內要設置在輸入框沒有值的時候才是 disabled

1
2
3
4
5
6
7
8
9
10
11
12
13
14

test("button should not be disabled when inputs exist", () => {
render(<Login />);
const buttonEl = screen.getByRole("button");
const usernameInputEl = screen.getByPlaceholderText(/username/i);
const passwordInputEl = screen.getByPlaceholderText(/password/i);

const testValue = "test";

fireEvent.change(usernameInputEl, { target: { value: testValue } });
fireEvent.change(passwordInputEl, { target: { value: testValue } });

expect(buttonEl).not.toBeDisabled();
});

css 補充

1
style={{ visibility: error ? "visible" : "hidden" }}

類似: display:none和visibility:hidden

而兩者差異:
visibility:的隱藏,是物件的位置仍舊保持著不會消失,
display: none, 會將 html 該物件拔除

小結

此練習範例,很請楚地知道在建立一個功能元件之後,如何再加入每一個區塊的測試,如標題文字、輸入框、按鈕的測試等等,也可以觀察到測試根據使用情境一步步的拆解並加入測試
下一篇會有串接資料搭配測試,敬請期待

參考資料
github-login
React Testing Tutorial with React Testing Library and Jest

使用 JavaSript 要進行測試最常使用的就是使用 Jest ,另外依據使用的前端框架來搭配使用進行測試。
以下內文就先以一些範例來直接學習

testing-library

  • 當使創建一個 react 檔案,會預設加入的測試範例檔

    1
    2
    3
    4
     test('renders learn react link', () => {
    const linkElement = screen.getByText(/learn react/i);
    expect(linkElement).toBeInTheDocument();
    });
  • screen : it represent the whole document which we have rendered

  • getByText: 取得 text

    • getByText(/learn react/i)
    • expect(linkElement).toBeInTheDocument();
    • 期待變數得值,toBeInTheDocument()
    • 檢測此項目是否存在於文件之中

撰寫測試的參考文件

ByRole
role attribute

加入列表(list)測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import logo from './logo.svg';

import './App.css';

import Login from './components/Login/Login';



function App() {



return (

<div className="App">

<header className="App-header">

<img src={logo} className="App-logo" alt="logo" />

<p>

Edit <code>src/App.js</code> and save to reload.

</p>

<a

className="App-link"

href="https://reactjs.org"

target="_blank"

rel="noopener noreferrer"
. >

Learn React

</a>

<ul>

<li>Apple</li>

<li>Banana</li>

<li>Kiwi</li>

</ul>


</header>

</div>

);

}

export default App;
1
2
3
4
5
test('renders 3 list items', () => {
render(<App />);
const listItems = screen.getAllByRole("listitem");
expect(listItems.length).toEqual(3);
});
  • jest expect
  • 需要時到官網看相關使用的API
    expect(listItems.length).toBe(3);
    expect(listItems.length).toEqual(3);

測試 title

  • getByTestId:運用此方法可以直接取得在元素上設置的 data-testid
  • getByTitle : 取得在標籤設置 title 的元素
1
2
3
4
5
<!-- const a = 2
const b = 1 -->

<h1 data-testid="mytestid">Hello</h1>
<span title="sum">{a + b}</span>
1
2
3
4
5
6
7
8
9
10
11
12
13
test('renders title', () => {

render(<App />);
const title = screen.getByTestId("mytestid");
expect(title).toBeInTheDocument();

});

test('sum should be 3', () => {
render(<App />);
const titleSum = screen.getByTitle("sum");
expect(titleSum.textContent).toBe("3");
});

運行測試

npm run test

  • 透過測試所提供的錯誤提示,可以得知預期的結果以及實際運行後得到的值之間的差異

參考資料:
[Day29] React Testing Library 的一些實用的小技巧
Next.js | 初探單元測試,使用 Jest + React Testing Library

Passing Data Deeply with Context

  • 當裡面的子狀態,會想要往父層去找尋時,就可以使用Context
  • 學習文件:React Docs
  • 資料會和其他元件要共用
    • prop drilling
      1. 最基本使用
    • 範例:Section.js \ Heading.js
    • children 帶入元件的內容

  1. 將 app.js 改寫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
return (
<Section>
<Heading level={1}>Title</Heading>
<Section>
<Heading level={2}>Heading</Heading>
<Heading level={2}>Heading</Heading>
<Heading level={2}>Heading</Heading>
<Section>
<Heading level={3}>Sub-heading</Heading>
<Heading level={3}>Sub-heading</Heading>
<Heading level={3}>Sub-heading</Heading>
<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}

以上方式,看到 在 Heading 元件傳入參數
改寫為,在 section 元件就來控制裡面的 Heading 是什麼 level

1
2
3
4
5
6
7
8
9
<Section level={3}>  

<Heading>About</Heading>

<Heading>Photos</Heading>

<Heading>Videos</Heading>

</Section>
  1. 實作
    1. Create a context.
    2. Use that context,在子層import { useContext } from 'react';
    3. 提供 context 的值

Create a context.

1
2
3
4
import { createContext } from 'react';

export const LevelContext = createContext(1);

Use that context

  • 是 Heading 要往外層找資料,所以要在這裡使用 useContext
  • 要往外層的地方
  • 注意 level
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}

Provide the context

  • 建立 context 的 provider
  • 提供此資料的提供者、元件
  • 這個 provider 是放在最外層,所以在此範例應該放在 section 去改寫
原本的 section.js
1
2
3
4
5
6
7
8
export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}

改寫section.js
  • level 從這邊拿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { LevelContext } from './LevelContext.js';  


export default function Section({ level, children }) {

return (

<section className="section">

<LevelContext.Provider value={level}>

{children}

</LevelContext.Provider>

</section>

);

}

首先要先了解 linked lists 和陣列是不一樣的!!
linked lists是一種常見的資料結構,其使用node(節點)來記錄、表示、儲存資料(data),並利用每個node中的pointer指向下一個node

題目:

You are given the heads of two sorted linked lists list1 and list2.

Merge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists.

Return the head of the merged linked list.

注意類似題目要設立一個 dummy 節點

  • 如何訪問list 中某個節點的 值,要從第一個節點開始,透過 next 依序訪問下一個節點重複操作直到抵達目標節點並讀取 value
  • 訪問第一個節點為 list.val ; 訪問第二個節點 list.next.val ; 訪問第三個節點list.next.next.val
  • 要將兩個 list 合併的方法,每次比較兩個 而最小的數挑出,放入新的 list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/

var mergeTwoLists = function (l1, l2) {
//current
const dummy = new ListNode(-Infinity)
//point dummy node
let prev = dummy;
while (l1 && l2) {
if (l1.val <= l2.val) {
prev.next = l1;
//移動 prev list
prev = l1
//鏈錶指針 l1 就往後移動
l1 = l1.next
} else {
prev.next = l2;
prev = l2
l2 = l2.next
}
}
//如果 l1 是 null ,就是 l2
if (!l1) prev.next = l2;
if (!l2) prev.next = l1;
return dummy.next
};

mergeTwoLists([1, 100], [3, 4, 6, 8])

對於刷題總時敬而遠之,記得剛開始學習 JavaScript 時,有挑幾題來練習,看到題目後完全不知道該如何著手,甚至有些題目還要反覆看幾次才比較理解。 畢竟也踏入前端工程師近一年的時間,使用 JavaScript 也有兩年了,該是好好鑽研練習 Leetcode。

題目:
Given a string s containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ and ‘]’, determine if the input string is valid.

An input string is valid if:

Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.
Every close bracket has a corresponding open bracket of the same type.

以字串帶入,並要確認傳入的字串中的大括號, 中括號等是否有兩倆配對

Example 1:

1
2
Input: s = "()"
Output: true
1
2
Input: s = "()[]{}"
Output: true
1
2
Input: s = "(]"
Output: false
  1. 製作一個物件,為括號的配對
  2. 存放括號的空物件
  3. 將字串進行迭代判斷
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
i 會loop 此字串
"([]){}"

第一圈:遇到 "("
stack = [ ")"]

第二圈:遇到 "["
stack = [ ")","]"]

第三圈:遇到 "]" , 此時,stack = [ ")","]"]
進入第二個判斷:stack[stack.length - 1] === ch
陣列的最後一個是否與 i=2 ,ch = "]" 互相配對,若配對成功則從陣列中刪除

第四圈:遇到 ")" , 此時,stack = [ ")"]
進入第二個判斷:stack[stack.length - 1] === ch
陣列的最後一個是否與 i=3 ,ch = ")" 互相配對,若配對成功則從陣列中刪除

第五圈:遇到 "{" , 此時,stack = []
stack = [ "}"]

第六圈:遇到 "}" , 此時,stack = [ "}"]
進入第二個判斷:stack[stack.length - 1] === ch
陣列的最後一個是否與 i=5 ,ch = "}" 互相配對,若配對成功則從陣列中刪除

最後跳出陣列確認 stack.length === 0 => 為 0 表示皆配對成功


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var isValid = function (s) {
const hashMap = { "(": ")", "{": "}", "[": "]" };
const stack = [];
for (let ch of s) {
if (hashMap[ch]) {
// ch is an opening bracket
stack.push(hashMap[ch]);
} else if (stack.length > 0 && stack[stack.length - 1] === ch) {
//當空陣列裡面有值且陣列的最後一個值和 key 是一樣的就從陣列中刪除
// ch is a closing bracket and top of stack matches
stack.pop();
} else {
// ch is a closing bracket and top of the stack doesn't match
return false;
}
}
return stack.length === 0;
};

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

之前曾快速的學習了React 資料的建立與傳遞方式,並寫了篇文章,最近因為六角讀書會活動也再次更深入一些的學習 React ,並將一些 React Hook 反覆使用練習。
因為目前工作主要還是以 Vue 進行開發,所以有時候會提一下兩者框架使用的相同或不同之處。

component 元件化

在 Vue 的撰寫中,也是會嘗試將頁面中的不同區塊做拆分。與 Vue 一樣,元件的的命名會以 大寫 為開頭。
透過減少重複為用意來建立元件化。


codepen-Component

多個資料,傳遞給單一元件

  • 建立兩個元件 component1 , component2
  • 建立共通元件 Board ,此元件可以各別放入兩個元件要傳入的資料(props)

codepen-共用元件

共同資料,傳遞給不同元件

codepen-共用資料

條件 render

  • 這部分在官方文件就有清楚介紹,可以依據使用條件來顯示元件資料
  • 使用條件判斷時,若不複雜,可以使用三元運算方式
    • isLoggedIn ? <UserGreeting /> : <GuestGreeting />
  • conditional-rendering

元件資料傳遞

子傳父

  • 範例將以 input 為範例
  • 此方式的資料傳遞,有點類似於 Vue 中的 v-model 資料雙向綁定
  • 使用 v-model 實現父子元件傳遞資料
  • 在 React 可以將 useState 中設訂的值綁在元件上作為傳遞
  • 在下方的圖示
  • 在父層有 value , setValue

-> 其中 setValue 方法提供給子元件,同時就會讓子元件也產生改變 value 的能力(也就是可以使用父元件的方法)

  • 要實現子傳父,就在父層掛一個 hook