安裝:
yarn create react-app reatc-lesson --template typescript
yarn create 創建一個react-app的應用 項目名稱 typescript 的模板
react-app 官方地址
https://create-react-app.bootcss.com/docs/adding-typescript
react 語法文檔
https://zh-hans.react.dev/learn#writing-markup-with-jsx
語法
基礎語法
組件函數
1、必須使用大駝峰命名;
2、return 之前可以定義組件使用的數據;
3、使用一對大括號即可使用定義對象的屬性;
4、reactNode 不支持直接渲染布爾值,布爾值用來條件渲染處理或者將其轉成字符串來使用。
5、reactNode 不支持直接渲染對象,需要將其轉為字符串才能直接渲染。
5、ReactNode 類型用來表示可以在React 組件中渲染的任何內容的一種類型,可以直接渲染:字符串、數字、元素或者或者包含這些類型的數組。
//return () 用來存放html組件的
function MyBanner(){//return 之前可以定義組件使用的數據const user = {name: 'Joe',}{/* */} //注釋語法{/* 返回組件的根元素*/}return(<h1 className>Hello MyBanner {user.name}</h1>)(/* 也可以不使用括號返回 */)return <h1 className>Hello MyBanner {user.name}</h1>
}
注釋
語法: {/* */}
{/* */}
添加class名稱
只能使用小駝峰命名的屬性名:className 來指定一個css 的class,使用方式跟class一樣
<img className="avatar" />
.avatar {border-radius: 50%;
}
根據數據渲染視圖
將數據放在元素標簽中,放到一對大括號里,例如:{user.name}
return (<h1>{user.name}</h1>
)
還可以將定義的數據放到元素屬性上,但是必須使用大括號 而非引號。例如, className={avatar} 將avatar 字符串傳遞給 className,作為css的class,而非通過變量傳遞給className。但 src={user.imageUrl} 會讀取js 的 user.imageUrl 這個變量,然后將其讀取的值作為 src 屬性傳遞
return (
<img className='avatar' src={user.imageUrl} />
)
一個組件中返回多個元素
1、在React語法中,要去一個組件的返回值只能有一個根元素。
2、使用div 包裹多個元素是一種常見的方法,但有時會導致不必要的DOM層次結構。
解決:引入 <></> 作為一種更簡潔的方法。
<></> 是React 中的一種稱為 Fragment 的語法。它是一種用于在組件中返回多個元素而不需要創建額外DOM元素的簡潔方式
return (<><h1>{user.name}</h1><imgclassName="avatar"src={user.imageUrl}alt={'Photo of ' + user.name}style={{color:'red'width: user.imageSize,height: user.imageSize}}/></>);
添加style樣式
語法:style={{}} ,是style={} 大括號內的一個普通 {} 對象。當樣式依賴 js 變量時,可以使用 style屬性
return (<><h1>{user.name}</h1><imgclassName="avatar"src={user.imageUrl}alt={'Photo of ' + user.name}style={{color:'red'width: user.imageSize,height: user.imageSize}}/></>);
使用style樣式的方式
function MyComponent(){const styles = {color:'red',fontSize:'16px',fontWeight:'bold'}return (<div style={styles}>這是一個文本</div>;)
}{/* 使用css模塊化文件 */}
import styles from './styles.module.css';function MyComponent() {return (<div className={styles.myClass}>這是一個文本</div>)
}
條件渲染
React沒有特殊語句來編寫條件語句,使用的就是普通的 js 代碼。例如:if
const user = {name: 'Joe',age: 32,isAdmin: false,isBanned: true,}let content;if(user.isAdmin){content = <h2>Welcome, {user.name}!</h2>}else {content = <h2>You are not an admin.</h2>}
或者通過組件:三目元算符
<div>{user.isBanned ? (<MyBanner />) : (<MyButton />)}</div>
又或者是 if 引入組件
let content;
if (isLoggedIn) {content = <MyBanner />
} else {content = <MyBanner2 />;
}
return (<div>{content}</div>
);
又或者是 &&
<div>{isLoggedIn && <AdminPanel />}
</div>
渲染列表
也是依賴js特性,例如for循環 和 map函數來渲染組件。
寫法1:組件外循環
const products = [{ title: 'Cabbage', id: 1 },{ title: 'Garlic', id: 2 },{ title: 'Apple', id: 3 },
]{/* 注意 li 里有一個key屬性。對應列表每一個元素,都應該傳遞一個字符串或數字給key,用于在其他 兄弟節點中唯一標識該元素,key是什么數據跟vue循環的key是一樣的 */}const listItem = user.products.map(item => <li key={item.id}>{item.title}</li>)<ul>{listItems}</ul>
寫法2,在組件內循環
<ul>{user.products.map((item,index) => (<li key={index}>{item.title}</li>))}
</ul>
寫法3:帶樣式
const itemList = user.products.map(item => <li key={item.id}style={{color: item.id % 2 === 0 ? 'red' : 'blue'}}>{item.title}</li>
)<ul>{itemList}
</ul>
for循環,沒其他寫法
const itemList2 = []for(let i = 0; i < user.products.length; i++){itemList2.push(<li key={i}>{user.products[i].title}</li>)}<ul>{itemList2}</ul>
響應事件
基本使用
也就是點擊事件咯,語法是 onClick={函數}
function App() {function handleClick (aaa :any){
{/* 在默認情況,事件監聽器的參數aaa 是一個事件對象(通常命名event,我現在命名aaa),這個事件包含事件類型、目標元素等 */}console.log('clicked',aaa) console.log(aaa.target) {/* 獲取目標元素*/}}return (<div className="App">{/* 基礎寫法1 */}<button onClick={handleClick}>點擊響應事件</button>{/* 內聯事件函數處理 */}<button onClick={function handleClick() {alert('hello')}}>OK1</button>{/* 簡潔箭頭函數 */}<button onClick={() => {alert('你點擊了我!');}}></div>);
}
事件監聽傳參
使用箭頭函數,在事件監聽中使用箭頭函數來傳遞參數。在箭頭函數中,可以訪問事件對象(入event)以及傳遞給事件監聽的其他參數
{/* 函數定義 */}
const handleClick2 =(aaa :Number) =>{console.log('clicked',aaa)return aaa
}或者function handleClick2 (aaa :Number){console.log('clicked',aaa)return aaa
}return (<button onClick={() => handleClick2(2)}>點擊傳值</button>
)
使用bind方法:
通過bind方法,可以綁定參數并創建一個新的函數,該函數將在事件觸發是被調用
{/* 函數定義 */}
const handleClick2 =(aaa :Number) =>{console.log('clicked',aaa)return aaa
}或者function handleClick2 (aaa :Number){console.log('clicked',aaa)return aaa
}return (<button onClick={handleClick2.bind(null,2)}>點擊傳值</button>
)
錯誤陷阱
錯誤1
傳遞事件處理函數的函數應該是直接傳遞,而非直接調用。
這個示例中,handleClick 作為一個 onClick 事件處理函數傳遞。這會讓React 記住,并且只在點擊按鈕的時候調用 傳遞的函數。
// 傳遞一個函數(正確寫法)
<button onClick={handleClick}></button>// 調用一個函數(錯誤寫法)
<button onClick={handleClick()}></button>
錯誤2
// 傳遞一個函數(正確)【alert 定義內聯事件函數,點擊的時候觸發】
<button onClick="{() => alert('...')}"></button>// 調用一個函數(錯誤)【這個 alert 在組件渲染時觸發,還不是在點擊時觸發】
<button onClick="{alert('...')}"></button>
其他常見響應事件
1、onChange 表單元素值發生變化觸發
當表單元素的值發生變化時觸發,比如輸入框的文本內容發生變化。
import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function inputUpChange (event: React.ChangeEvent<HTMLInputElement>){const {name,value} = event.targetconsole.log(name,value) //打印 nanme屬性名,value 輸入值setPerson({...person, [name]: value}) //設置值}return (<div><h4>當前信息{JSON.stringify(person)}</h4><input type="text" name='name' value={person.name} onChange={inputUpChange} /><input type="text" name='age' value={person.age} onChange={inputUpChange} /></div>);
}
2、onSubmit 表單提交時觸發
點擊按鈕時觸發 form表單 提交函數
submitUserInfo
注意:在表單上使用 onSubmit 事件,并沒有阻止默認行為,它將觸發表單的默認提交行為,導致頁面刷新。
import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function submitUserInfo(){console.log('submit',user)}return (<div><h4>當時onSubmit 信息</h4><form onSubmit={submitUserInfo}><input type="text" name='name' value={person.name} onChange={inputUpChange} /><button type='submit'>點擊觸發submit事件</button></form></div>);
}
為了阻止默認行為,可以在onSubmit 事件處理函數中調用
event.preventDefault()
方法。將阻止表單的默認提交行為,從而避免頁面刷新。
下面的代碼,在用戶點擊提交按鈕時候,
submitHandle
函數將被調用,并且e.preventDefault()
將阻止表單的默認提交行為,從而避免頁面刷新。可以在submitHandle
函數中執行提交表單的邏輯。
在沒有使用
preventDefault
的情況下,打印的對象和數組無法展開的,因為在提交后表單的默認行為會導致刷新
import React,{useState} from 'react';const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]
function StatesFormBox() {const [iibb,setIibb] = useState(initialList)function submitHandle(e: any){e.preventDefault()console.log(e)console.log(666,iibb)}function aa(){setIibb([{ id: 3, title: 'Terracotta Army', seen: false }])}return (<div>{/* <h1>State Form</h1> */}<form onSubmit={submitHandle}><button type='submit'>點擊按鈕提交</button></form></div>)
}export default StatesFormBox;
3、onMouseEnter 當鼠標移入元素時觸發
import React,{useState} from 'react';function App() {function handleMouseEnter(){console.log('鼠標移入元素了')}return (<div style={{width:'100px',height:'100px',border:'1px solid green'}} onMouseEnter={handleMouseEnter}><h4>鼠標進入</h4></div>);
}
4、onMouseLeave 當鼠標移出元素時觸發
import React,{useState} from 'react';function App() {function handleMouseLeave(){console.log('鼠標移出元素了')}return (<div style={{width:'100px',height:'100px',border:'1px solid green'}} onMouseLeave={handleMouseLeave}><h4>鼠標移出</h4></div>);
}
5、onKeyDown 當按下鍵盤上的任何信息時觸發
使用方法更上面類型
<input type="text" onKeyDown={handleKeyDown} />
6、onKeyUp 當釋放鍵盤上的任意鍵時觸發
<input type="text" onKeyUp={handleKeyUp} />
7、onFocus 當元素獲取焦點時觸發
<input type="text" onFocus={handleFocus} />
8、onBlur 當元素失去焦點時觸發
<input type="text" onBlur={handleBlur} />
9、onScroll 當前元素滾動時觸發
<div onScroll={handleScroll}>滾動時觸發</div>
子組件接收父組件的child,類型vue的v-text
import React,{useState} from 'react';
import Gallery from './Gallery';function App() {function clickHandle(num: number, num2: number){console.log(num + num2)return num + num2}function MyButton4({onClick, children}:{onClick:(num:number,num2:number)=>number,children:string}){return (<div>{/* 渲染會顯示 <button onClick={()=> onClick(2,2)}>我是傳遞</button> 的按鈕 */}<button onClick={()=> onClick(2,2)}>{children}</button></div>)}return (<div><MyButton4 onClick={clickHandle}>我是傳遞</MyButton4></div>);
}
更新界面
就是更改數據,更新視圖,數據驅動視圖
1、從useState 中獲得兩樣東西:當前的state(count),以及更新值的函數(setCount)。也可以起任何名字,但是慣例會像這樣:[something, setSomething] 這樣命名
2、第一次顯示,count 的值默認為0,因為你
import React from 'react';
import { useState } from 'react';
優化后一行搞到:
import React,{useState} from 'react';function App() {{/* 從useState 中獲得兩樣東西:當前的state(count),以及更新值的函數(setCount)。 */}const [count, setCount] = useState(0); {/* 默認值0*/}{/* 自定義命名 */}// 聲明一個num的狀態變量,并初始化為 2const [num, setNum] = useState(2); {/* 默認值2 */}function updateClick(){setCount(count + 1)}function updateNum(){setNum(num + 1)}return (<div className="App"><button onClick={updateClick}>點擊 {count} 了</button><button onClick={updateNum}>點擊了num值{num}了</button></div>);
}
Hook
再React中,以 use 開頭的函數都被稱為 Hook。 useState 是React 提供的內置 Hook 函數。
Hook 比普通函數更為嚴格。只能在組件(或者其他Hook)的頂層 調用 Hook。如果要在一個條件或者循環中使用 useState,需要在新的組件并在內部使用它。
注意
Hooks ---->以
use
開頭的函數 -----> 只能在組件活自定義 Hook的最頂部調用。 不能在條件語句、循環語句或者其他嵌套函數內調用 Hook。Hook是函數,但將其視為關于組件需求的無條件聲明。
//useState 的唯一參數是 state 變量的 初始值。在這個例子中,index的初始值被 useState(0) 設置為0
const [index, setIndex] = useState(0)
渲染步驟
- **組件進行第一次渲染。**因為你將
0
作為index
的初始值傳遞給useState
,它將返回[0, setIndex]
。React 記住0
是最新的 state值。 - 你更新了state。 當點擊按鈕時,調用
setIndex(index +1)
。index
是0
,所以它是setIndex(1)
。這告訴 React 現在記住index
是1
并觸發下一次渲染。 - 組件進行第二次渲染。 React 仍然看到
useState(0)
, 但是因為 React 記住了你將index
設置為了1
,它將返回[1, setIndex]
。 - 以此類推
組件共享
在子組件里使用父組件傳的方法和變量數據
import React,{useState} from 'react';
import Gallery from './Gallery';// {person,size} 組件使用時的屬性,要一一對應,對接收的值類型驗證
function MyButton1({person,size}:{person:Object,size:number}) {return (<button>按鈕一號:{JSON.stringify(person)}---{size > 1 ? 1 : 2}</button>)
}
// onClick 是組件使用的屬性名,冒紅后面的對象是對這個函數的描述的類型解析
function MyButton2({onClick}:{onClick:() => void}) {return (<button onClick={onClick}>按鈕二{num}號</button>)
}
//接收一個屬性,對函數執行的時候參數和返回值的要去
function MyButton3({onClick}:{onClick:(num:number,num2:number)=>number}){return (<div><button onClick={()=> onClick(2,2)}>按鈕三</button></div>)
}function App() {const [num, setNum] = useState(2);return (<div><MyButton1 person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}size={100} /><MyButton2 onClick={updateNum} /><MyButton3 onClick={clickHandle} /></div>);
}
組件
定義組件
function Profile(){return (<imgsrc='https://www......'/>)
}
組件的導入導出
export 居然導出,export default 默認導出
function Profile(){return (<imgsrc='https://www......'/>)
}
// export 居然導出
export default function Gellery(){return (<h1>open111</h1><Profile />)
}
import 組件 from ‘組件文件地址’ =》 import Gallery from ‘./Gallery’
Gallery.tsx 導出
function Profile() {return (<imgsrc="https://i.imgur.com/QIrZWGIs.jpg"alt="Alan L. Hart"/>);}export default function Gallery(){return (<><h1>開始了</h1><Profile /><Profile /></>)}
app.tsx 導入
引入過程中,import Gallery from ‘./Gallery’; ,后綴.jsx添加與否都能正常使用。
import Gallery from './Gallery';function App() {return (<div><Gallery /><Gallery /></div>);
}
導入導出注意點
從一個文件中導出和導入多個文件
//用具名方式導出
export function Profile(){//****
}
//接著,具名導入的方式,從文件到當前組件文件中(用大括號)
import {Profile} from './Gallery.tsx'//渲染
export default function App() {return <Profile />;
}
//用默認導出的方式
export default function Gallery() {return (<section><h1>了不起的科學家們</h1></section>);
}// 導入 默認導出的組件
import Gallery from './Gallery.tsx';//渲染
export default function App() {return <Gallery />;
}
嵌套組組件
組件里可以渲染其他組件,但是 請不要嵌套定義組件的定義。下面這段代碼 非常慢,并且還會導致bug產生
export default function Gallery() {// 🔴 永遠不要在組件中定義組件function Profile() {// ...}// ...
}
正常使用
export default function Gallery() {// ...
}// ? 在頂層聲明組件
function Profile() {// ...
}
組件記憶:雙向綁定
組件通常需要通過 交互更改屏幕上顯示的呢絨。輸入表單 應該更新輸入字段,點擊輪播圖上的 “下一個”應該更改顯示的圖片,點擊 “購買” 應該將商品放入購物車。組件需要 “記住” 某些東西:當前輸入值、當前圖片、購物車等。值React中,這種組件特有的記憶稱為 state。
普通的變量的值改變時,更新變量的值時,組件沒有出現數據驅動視圖
普通變量無法驅動改變視圖
點擊按鈕,變量的值更新了,但是視圖沒有變化。
注意
updateAgeHandle()
事件處理函數整個更新局部變量 age,有兩個原因使得視圖沒有更新1、**局部變量無法在多長渲染中持久保持。**當React 再次渲染這個組件時,會從頭開始渲染,不會考慮之前對局部變量的任何更改。
2、**更改局部變量不會觸發渲染。**React 沒有意識到它需要使用新數據再次渲染組件。
function App() {const user = {name: 'Joe',age: 32,}function updateAgeHandle(){user.age += 1console.log('age',user.age)}return (<div><h4>當前年齡:{user.age}</h4><button onClick={updateAgeHandle}>更新年齡</button></div>);
}
方案
要使用新數據更新組件,需要做兩件事
1、保留 渲染之間的數據。
2、觸發 React 使用新數據來渲染組件(重新渲染)
解決
useState Hook 提供了這兩個解決功能
1、State 變量 用于保存渲染間的數據。
2、State setter 函數 更新變量并觸發 React 再次渲染。
實現
// 要添加 state 變量,先從文件頂部導入 useState
import {useState} form 'react'// 然后 將局部變量定義的代碼換成state 變量
//替換后的 index 是一個state變量,setIndex 是對應的 setter 函數。
let index = 0; 【將其修改為】====>>> const [index, setIndex] = useState(0) //初始變量0//函數中觸發
function updateAgeHandle(){setIndex(index + 1)
}
State 是隔離且私有的
State 是屏幕上組件實例內部的狀態。也就是說,**如果你渲染同一個組件兩次,每次副本都會有完全隔離的 state!**其中一個組件的state不會音響另外一個。
State 定義對象
看了這么多state 變量的定義,還是不太明白對象數據驅動視圖怎么弄。
如下實現 表單輸入更新
import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function inputUpChange (event: React.ChangeEvent<HTMLInputElement>){const {name,value} = event.targetconsole.log(name,value) //打印 nanme屬性名,value 輸入值setPerson({...person, [name]: value}) //設置值}return (<div><h4>當前信息{JSON.stringify(person)}</h4><input type="text" name='name' value={person.name} onChange={inputUpChange} /><input type="text" name='age' value={person.age} onChange={inputUpChange} /></div>);
}
state 中更新數組
當操作 React state 中數組是時,你需要避免使用左列的方法,而首選右列的方法
避免使用 (會改變原始數組) | 推薦使用 (會返回一個新數組) | |
---|---|---|
添加元素 | push ,unshift | concat ,[...arr] 展開語法(例子) |
刪除元素 | pop ,shift ,splice | filter ,slice (例子) |
替換元素 | splice ,arr[i] = ... 賦值 | map (例子) |
排序 | reverse ,sort | 先將數組復制一份(例子) |
添加元素
import React,{useState} from 'react'function ArrayDomState() {const [arr, setArr] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function add1ArrHandle() {setArr([...arr,11])}function add2ArrHandle() {setArr(arr.concat(11))}return (<>{/* 添加元素 */}<div><p>array 數據:{JSON.stringify(arr)}</p><button onClick={add1ArrHandle}>state 擴展運算符[...arr] 添加 state 數組數據</button></div><div><p>array 數據:{JSON.stringify(arr)}</p><button onClick={add2ArrHandle}>使用 concat 添加 state 數組數據</button></div></>)
}export default ArrayDomState
刪除元素
import React,{useState} from 'react'function ArrayDomState() {const [arr, setArr] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function delete1ArrHandle() {const newArr = arr.filter((item) => item !== 1)setArr(newArr)}function delete2ArrHandle() {const newArr = [...arr]newArr.splice(1, 1);setArr(newArr)}return (<>{/* 刪除元素 */}<div><p>arr 刪除元素{JSON.stringify(arr)}</p><button onClick={delete1ArrHandle}>state 使用 filter 刪除數組數據</button></div><div><p>arr 刪除元素{JSON.stringify(arr)}</p><button onClick={delete2ArrHandle}>state 使用 splice 刪除數組數據</button></div></>)
}export default ArrayDomState
轉換數組
這種方式就是轉換數組,就是使用 setState() 方法來更新組件的state,從而實現對數據的轉換操作。
const newArr = [...arr]
newArr.splice(1, 1);// 使用新的數組進行重渲染
setArr(newArr)
替換數組中的元素
import React,{useState} from 'react'function ArrayDomState() {const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function arraysDomHandle() {const updateArr = arrs.map((item) => {if(item === 3){return 333333333} else {return item}});setArrs(updateArr)}return (<div><div><p>array 數據:{JSON.stringify(arrs)}</p><button onClick={arraysDomHandle}>點擊替換數組中的元素</button></div></div>)
}export default ArrayDomState
向數組中間插入元素
向數組特定位置插入一個元素,這個位置既不在數組開頭也不在數組末尾。
import React,{useState} from 'react'function ArrayDomState() const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function insertArrHandle(){const insertIndex = 5;const newArr = [...arrs.slice(0, insertIndex),333333333,...arrs.slice(insertIndex)]setArrs(newArr)}return (<div>{/* 向數組中插入元素 */}<div><p>array 數據:{JSON.stringify(arrs)}</p><button onClick={insertArrHandle}>點擊向數組中插入元素</button></div></div>)
}export default ArrayDomState
其他更改數組的情況
總有些事情,是僅靠展開運算符和
map()
或者filter()
等不會直接修改原值的方法能做到的。例如翻轉數組,或者數組排序,而 javaScript 中的 reverse() 和 sort() 方法會改變原數組,所以不能直接使用她們。解決:先拷貝這個數組,然后再改變拷貝數組的值。
import React,{useState} from 'react'function ArrayDomState() const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function changeArrHandle(){const newArr = [...arrs];newArr[0] = 333333333;newArr.reverse()setArrs(newArr)}return (<div>{/* 其他改變數組的情況 */}<div><p>array 數據:{JSON.stringify(arrs)}</p><button onClick={changeArrHandle}>點擊改變數組中的元素</button></div></div>)
}export default ArrayDomState
問題:
在上面的代碼中,雖然使用
[...arrs]
展開運算符創建了一份數組的拷貝值。當有了拷貝值后,就可以使用newArr.reverse()
或者newArr.sort()
這樣修改原數組的方法。 甚至可以通過newArr[0] = 333333333
這樣的方式對特定元素進行賦值。
但是,這種拷貝方式,只能適用于 基礎類型的數組,不適用對象數組的元素。 原因大家應該都知道,這種解構的方式是淺拷貝,新數組種的對象依然與原始對象數組的原始的內存地址。因此,如果你修改了拷貝數組內部的某個對象。
//雖然 nextList 和 list 是兩個不同的數組,nextList[0] 和 list[0] 卻指向了同一個對象。因此,通過改變 nextList[0].name,list[0].name 的值也會被改變const nextList = [...list]
nextList[0].name = 'tom'
setList(nextList)
更新數組對象的元素
import React,{useState} from 'react'const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]function ArrayDomState() {const [myList, setMyList] = useState(initialList)function handleToggleMyList(artworkId :number, nextSeen: any) {setMyList(myList.map((item) => {if(item.id === artworkId){// 創建包含變更的 新對象return {...item, title: nextSeen}}else {return item}}))}return (<div>{/* 更新數組內部對象的值 */}<div><p>更新數據{JSON.stringify(myList)}</p><button onClick={() => handleToggleMyList(1,'修改咯')}>更新對象數組</button></div></div>)
}export default ArrayDomState
使用Immer 編寫簡單的更新
import React,{useState} from 'react'
import { useImmer} from 'use-immer'const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]function ArrayDomState() {const [myList2, setMyList2] = useImmer(initialList)function handleToggleMyList2(artworkId :number, nextSeen: any) {setMyList2(draft => {console.log('draft',JSON.stringify(draft))const artwork = draft.find((item) => item.id === artworkId) console.log('artwork', JSON.stringify(artwork))if(artwork) artwork.title = nextSeenconsole.log('artwork222',JSON.stringify(artwork))})}return (<div>{/* 使用Immer 編寫更加簡潔的更新邏輯 */}<div><p>使用 Immer 編寫:{JSON.stringify(myList2)}</p><button onClick={() => handleToggleMyList2(2,'Immer')}>使用Immer點擊</button></div></div>)
}export default ArrayDomState
想修改對象數組的值,還得先拷貝一份。
使用Immer 時,類似
artwork.seen = nextSeen
這種會產生 mutation的語法不會再有任何問題了:
updateMyTodos(draft => {const artwork = draft.find(a => a.id === artworkId);artwork.seen = nextSeen;
});
狀態管理
React 狀態管理是指在React 應用中有效地管理和共享組件之間的狀態。 React 也提供了一些內置的狀態管理:例如 使用組件本地的狀態( state) 和屬性 ( props ),以及使用上下文 ( context )進行狀態共享。
隨著應用不斷變大,應用變得更加復雜,這些內置的狀態管理機制可能會變得不夠靈活或難以維護。沉余或者重復的狀態往往是缺陷的根源。 為了解決這個問題,通常會使用 Redux、Mobx 或者 React Context API 等
使用State 狀態相應輸入
在react種,不用直接從代碼層面上修改UI,不用編寫諸如 “禁用按鈕”、“啟用按鈕”、“顯示成功消息” 等命令。只需要描述組件在不同狀態(“初始狀態”、“輸入狀態”、“成功狀態”)下希望展示的UI,然后根據用戶輸入觸發狀態變更。
使用React 編寫的反饋表單,根據 status 這個狀態變量來決定顯示提交按鈕以及 是否顯示成功消息
import React,{useState} from 'react';
function StatesFormBox() {const [age, setAge] = useState('')const [error, setError] = useState('')const [status, setStatus] = useState('typind')if(status === 'success'){return <div>Success</div>}async function submitHandle(e: any){e.preventDefault()setStatus('loading')try {await submitForm(age)setStatus('success')} catch(error) {console.log(error)setStatus('typind')setError('error')}}function setAgeHandle(e:any){setAge(e.target.value)}return (<div>{/* <h1>State Form</h1> */}<div>{age}==={error}----{status}</div><form onSubmit={submitHandle}><textarea value={age} onChange={setAgeHandle}></textarea><button type='submit'>點擊按鈕提交</button></form></div>)
}function submitForm(age: string){return new Promise((resolve,reject) => {setTimeout(() => {console.log(age,age === '18')if(age !== '18') {reject(new Error('年齡不正確'))}else{resolve('提交成功')}},2000)})
}export default StatesFormBox;
在組件共享狀態
import React,{useState} from 'react';function Panel({title,children,isActive,onShow}:{title:string,children:string,isActive:boolean,onShow:()=>void}){return (<><h3>{title}</h3>{isActive ? (<p>children</p>) :(<button onClick={onShow}>顯示</button>)}</> )
}function StatesFormBoxShare() {const [activeIndex, setActiveIndex] = useState(0)function setActiveHandle(value:number){setActiveIndex(value)}return(<><Panel title='標題' isActive={activeIndex === 0} onShow={() => setActiveHandle(0)}>112313</Panel><Panel title='標題二' isActive={activeIndex === 1} onShow={() => setActiveHandle(1)}>22222</Panel></>)
}export default StatesFormBoxShare
useReducer 的使用
在hooks中提供了 useReducer 功能,可以增強 ReducerDemo 函數提供類似 Redux的的功能。
useReducer 能接受一個 reducer 函數 作為參數,reducer 接受兩個參數,一個是state 另外一個是action。然后返回一個狀態 count 和 dispath,count 是返回狀態中的值,而 dispath 是一個可以發布事件來更新 state 的。
基本使用
import React,{useReducer} from 'react'export default function ReducerDemo() {const [count, dispath] = useReducer((state,action)=> {//...}, 0);return (<div><h1 className="title">{count}</h1></div>)
}
要點
reducers 不應該包含異步請求、定時器、或者任何副作用(對組件外部有影響的操作),應該以不可變值的方式去更新對象和數組
修改對象
下面就是useReducer 更新的使用。
事件處理程序只通過派發 action 來 指定 發生了什么,而 reducer 函數通過 響應 actions 來決定 狀態如何更新
import React,{useReducer} from 'react'//(1)初始的數據
const initInfoData = {name : '張三',age : 18,sex : '男',
}//(2) 定義組件
function UseReducerBox (){//(3) useReducer 接受一個reducer參數:【reducerFun 自定義函數,自定義的這個reducer函數reducerFun 接受兩個參數:一個是state 另一個是action。】;useReducer 接受的第二個參數:【initInfoData 就是初始的 state 數據,就是初始數據】// useReducer 返回一個狀態 count:【userInfo】和 dispath:【setUserInfo】,userInfo 是返回狀態中的值,而 setUserInfo 是一個可以發布事件來觸發更新state的// count 和dispath 是官方示例的命名const [userInfo, setUserInfo] = useReducer(reducerFun,initInfoData)//(7) 點擊函數 觸發 發布事件來更新state 的。function handleClick(){//觸發發布更新后,useReducer 第一個參數就會執行了。setUserInfo('edit')}//(5)定義組件return (<><h5>useReducer 修改對象</h5><div>{JSON.stringify(userInfo)}</div>{/*(6) 觸發點擊*/}<button onClick={() => handleClick()}>點擊設置</button></>)
}//(3) 定義 useReducer 的第一個reducer參數,接收兩個參數 一個是 state 一個是action
function reducerFun(state :any, action :any){//(4) state 當前狀態下的數據,action為接收 setUserInfo 這個更新state的參數console.log(state, action) // 打印:{name: '張三', age: 18, sex: '男'} 'edit'//這個if可以不用if(action === 'edit'){//返回修改后狀態數據return {name : '李四',age : 20,sex : '女'}}
}export default UseReducerBox
useState 和 useReducer 的對比
代碼體積 :
通常,在使用
useState
的時候,開始的時候只需要寫少量的代碼。 而useReducer
必須提前編寫reducer
函數和需要調度的 actions。 但是在多個事件處理程序以相似的方式修改 state 的時候,useReducer
可以減少代碼量。
可讀性:
狀態更新邏輯足夠簡單的時候 useState 的可讀性還可以,但是一旦邏輯變動復雜起來,就會使得代碼變得臃腫難以閱讀。這種情況下,useReducer 可以將狀態更新邏輯和 事件處理程序分離。
可調試性:
使用 useState 出現問題,必須單步執行更多代碼。而使用 useReducer 的時候,可以在 reducer 函數中通過打印日志的方式來觀察每個狀態的更新,以及為什么更新(來自哪個action)。如果所有的action都沒問題,就知道問題出在 reducer本身的邏輯了。
區分:
useState 是React 中 最簡單的狀態管理方法。使用簡單的對象來存儲狀態,并提供兩個方法來訪問和更新狀態
useReducer 提供了一種更加復雜的狀態管理方法。使用一個reducer 函數來處理狀態更新,并提供一個 dispatch() 方法來觸發狀態更新。
useState
關鍵區別在于如何處理狀態更新。 useState 使用簡單的對象來存儲狀態。這使得它很好使用,但也可能導致性能問題,因為每次狀態更新都會重新渲染組件。
useReducer
使用一個reducer函數 來處理狀態更新。這使得可以更有效地處理復雜的狀態更新,因為可以避免不必要的重新渲染。但是 useReducer 也更復雜,需要更多的學習和理解才能使用。
大多數情況下 useState 是足夠來管理簡單的狀態。但是,如果需要處理復雜的狀態更新,則 useReducer 可能是更好的選擇
實驗Immer 簡化 reducers
這與平常的 state 中 修改對象和數組一樣,可以使用Immer 庫來簡化 reducer。useImmerReducer 讓可以通過 push 或者 arr[ i ] = 來修改state
import React,{} from 'react'
import { useImmerReducer } from 'use-immer'const initInfoData = {name : '張三',age : 18,sex : '男',
}function ImmerReducerBox (){const [userInfo, setUserInfo] = useImmerReducer(reducerFun,initInfoData)function handleClick(obj: any){setUserInfo(obj)}return (<><h5>ImmerReducerBox 簡化 useReducer 對象</h5><div>{JSON.stringify(userInfo)}</div><button onClick={() => handleClick({type:'setName',payload:'大豆'})}>點擊設置姓名</button><button onClick={() => handleClick({type:'setAge',payload:'19'})}>點擊設置年齡</button><button onClick={() => handleClick({type:'setSex',payload:'女'})}>點擊設置性別</button></>)
}function reducerFun(draft: any,action :any){switch(action.type){case 'setName':draft.name = action.payloadbreakcase 'setAge':draft.age = action.payloadbreakcase 'setSex':draft.sex = action.payloadbreakdefault:break}}export default ImmerReducerBox
使用 Context 深層次傳遞參數
描述
通常使用 props 將信息從父組件傳遞到子組件。但是,如果必須通過許多中間件向下傳遞props,或者在應用中的許多組件需要相同信息,傳遞props 會變得十分冗長和不變。Context 允許父組件向其下層無論多深的任何組件提供信息,無需通過props 顯示傳遞
props 傳遞帶來的問題
props 傳遞 是將數據通過 UI 樹顯式傳遞到 子組件的好方法。
但是當需要在組件樹深層遞參數以及需要在組件間復用相同的參數時,傳遞 props 就會變得很麻煩。最近的根節點的父組件可能離需要的組件很遠,狀態提升 到太高的層級會導致 逐層傳遞 props 的情況
Context的基本使用
import React,{createContext,useContext} from 'react'const initInfoData = {name : '張三',age : 18,sex : '男',
}
function Childrens() {return (<><div>第一個子元素</div><Childrens2 /></>)
}
function Childrens2() {const aaa = useContext(conTextS)return (<div>第二個子元素{JSON.stringify(aaa)}</div>)
}const conTextS = createContext(initInfoData)
function ContextBox (){return (<><div>1232222</div><Childrens /></>)
}export default ContextBox