青島學(xué)網(wǎng)站建設(shè)的大學(xué)搜索關(guān)鍵詞技巧
一、準(zhǔn)備工作
本文略長(zhǎng),建議耐心讀完,每一節(jié)的內(nèi)容與上一節(jié)的內(nèi)容存在關(guān)聯(lián),最好跟著案例過(guò)一遍,加深記憶。
1.1 創(chuàng)建項(xiàng)目
- 第一步,執(zhí)行下面的命令來(lái)創(chuàng)建一個(gè) React 項(xiàng)目。
npx create-react-app react-example
cd react-example
- 第二步,安裝依賴,運(yùn)行項(xiàng)目
yarn install 或 npm install
yarn start 或 npm run start
1.2 項(xiàng)目結(jié)構(gòu)
如圖:
1.3 初始化
將 src/index.js
的默認(rèn)代碼刪掉,保留下面這部分。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {return <div>Hello</div>
}
root.render(<App />);
現(xiàn)在項(xiàng)目看起來(lái)就像這樣,一個(gè)簡(jiǎn)單的 Hello
。
二、React 的基本用法
如果你還不熟悉 React 的基礎(chǔ)語(yǔ)法,可以閱讀我前面寫(xiě)的 React & 工作日常語(yǔ)法。
1.1 輸出 Hello, world
第一步肯定是要先來(lái)句 Hello,world!
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {return <div>Hello, world!</div> // 改動(dòng)點(diǎn)
}
root.render(<App />);
1.2 組件的使用
組件
可以由函數(shù)或者類來(lái)進(jìn)行創(chuàng)建,像上面的App
函數(shù)就是一個(gè)組件,里面可以混合寫(xiě) HTML & JS 代碼,React 會(huì)自動(dòng)幫我們解析這些語(yǔ)法,像這種寫(xiě)法就叫就JSX
。
下面我將以函數(shù)的方式來(lái)定義組件作為案例,類組件在講生命周期時(shí)做演示。
- 第一步,隨便定義一個(gè)叫
List
的組件,內(nèi)容隨便寫(xiě),比如 Hi List
const List = () => {return <div>Hi List.</div>
}
- 第二步,引入
List
,目前入口只有App
,很明顯只能放這里。
const App = () => {return <div>Hello, world! </List> </div>
}
效果:
1.3 組件嵌套組件
像
App
組件嵌套List
組件,我們管這叫父子組件
,App 是父組件, List 是子組件,當(dāng) List 套上 Item 組件時(shí),那 Item 的父組件就是 List,而祖父組件是 App, 以此類推。
現(xiàn)在我們來(lái)給 List
嵌套一個(gè) Item
組件。
const List = () => {// 多行時(shí)要用 () 包裹return (<ul><Item/> // 引入</ul>)
}
const Item = () => {return <li>item1</li>
}
效果:
別忘了還可以寫(xiě) JSX 的,現(xiàn)在試試用 Array.map 循環(huán)多個(gè) Item 組件。
const List = () => {return (<ul>{[1, 2, 3, 4].map((num) => Item(num) )}</ul>)
}
const Item = (num) => {return <li key={num}>item{num}</li>
}
在看效果前思考下,上面代碼具體會(huì)發(fā)生哪些變化?
- Item 函數(shù)組件多了個(gè)
num
參數(shù),我們用它來(lái)接受,并用{num}
括號(hào)包括起來(lái)進(jìn)行訪問(wèn) <Item/>
的調(diào)用方式變成Item(num)
,因?yàn)槲覀円獙?num 值傳遞給 Item 函數(shù),所以我們又發(fā)現(xiàn)了一個(gè)特性,在不傳參的情況下直接聲明<Item/>
是會(huì)自動(dòng)觸發(fā)函數(shù)的。- 循環(huán)組件時(shí),
key
屬性得帶上且是唯一的。
效果:
1.3 組件綁定點(diǎn)擊事件
干巴巴的列表,沒(méi)有交互行為怎么能行呢,
現(xiàn)在我們就來(lái)定義一個(gè)事件,當(dāng)點(diǎn)擊某個(gè) Item 時(shí)提示對(duì)應(yīng)的 num 數(shù)值,代碼如下:
const onClickItem = (num) => {alert(num);
}
const Item = (num) => {return <li key={num} onClick={() => onClickItem(num)}>item{num}</li>
}
解釋:
- 點(diǎn)擊事件用
onClick
駝峰形式來(lái)表示,其它事件也是類似的,比如 onFocus, onMouse 等。 onClick={() => 函數(shù)}
花括號(hào)返回一個(gè)箭頭函數(shù),函數(shù)里面就是返回我們定義的函數(shù),后面點(diǎn)擊時(shí)會(huì)觸發(fā)。
不用懷疑語(yǔ)法是否有問(wèn)題,連 JSX 語(yǔ)法都有了,你還在乎這個(gè)?
效果:
1.5 響應(yīng)式數(shù)據(jù)
光彈窗沒(méi)啥用啊,要不點(diǎn)擊后直接變更 Item 的數(shù)據(jù)如何?
問(wèn)題是… 怎么變更?直接將 num = xxx?熟悉 JS 的朋友應(yīng)該知道,當(dāng) num 參數(shù)傳遞的是【基本類型】時(shí)只是一個(gè)副本,更改后對(duì)原來(lái)的 num 是無(wú)效的。
就算有效,你又如何將視圖中的 num 也發(fā)生變化呢? 看來(lái)還是得借助 React 提供的語(yǔ)法了。
唉沒(méi)辦法,學(xué)吧~
-
第一步,先將原來(lái)的
[1,2,3,4]
以useState
提供的函數(shù)來(lái)定義并導(dǎo)出arr
變量數(shù)組,如圖:
效果還是與原來(lái)一樣,這里就不貼圖了。 -
第二步,在 arr 后面再聲明一個(gè)
setArr
函數(shù),名字隨便定義,但一般用 set 開(kāi)頭作為規(guī)范好點(diǎn),表示用來(lái)變更 arr 數(shù)據(jù)的。
const List = () => {const [arr, setArr] = useState([1, 2, 3, 4])
// ...省略
- 第三步,觸發(fā)
setArr
函數(shù),更改 arr 數(shù)組里的數(shù)據(jù)來(lái)達(dá)到我們想要的視圖變更效果
由于setArr
是在 List 函數(shù)組件里定義的,其它函數(shù)無(wú)法直接訪問(wèn),得通過(guò)傳參的方式帶過(guò)去、一直傳到 onClickItem 中,有點(diǎn)繞,將就一下吧,相信你不介意的(/逃)。
這里我們重點(diǎn)關(guān)注這一段代碼:
setArr(state => {const arr = [...state];arr[1] = 1000;return arr;
})
當(dāng)點(diǎn)擊后,將 arr[1] 改為 1000,并將新的 arr 返回出去,效果如圖:
你可能注意到,state 就是原先的 arr,然后我們用擴(kuò)展符號(hào)將它拷貝一份到新的 arr 中,為什么要這樣做呢?直接 state[1] = 1000
再 return 出去不行嗎,多此一舉嘛這不是。
是可以這樣做,數(shù)據(jù)也會(huì)發(fā)生變化了,但視圖不會(huì)發(fā)生變化,因?yàn)?React 明確規(guī)定:state 是不可變數(shù)據(jù),你不能直接改變它,而是要用一份副本去改變
。
為什么 state 要堅(jiān)持不可變?cè)瓌t呢?官方也說(shuō)了,當(dāng)你要實(shí)現(xiàn)一個(gè)撤銷&恢復(fù)的功能時(shí)就沒(méi)轍了,或者說(shuō)實(shí)現(xiàn)起來(lái)更復(fù)雜?React 這時(shí)要是還允許你去那豈不是有點(diǎn)不地道了,這不跟吃了上頓不考慮下頓的道理嘛。
小結(jié):
- useState 可以聲明響應(yīng)式數(shù)據(jù)。
- state 數(shù)據(jù)不可變,要用副本代替,遵循不可變?cè)瓌t。
1.6 什么是鉤子函數(shù) Hook Function
其實(shí)我們已經(jīng)用過(guò)鉤子函數(shù)了,上面的 useState
就是一個(gè)鉤子函數(shù),React 內(nèi)置了許多的鉤子,比如 useEffect, useRef
等,這里就不一一介紹,只需明白以 use
為開(kāi)頭的均屬于鉤子函數(shù)即可。
我們也可以自定義鉤子,問(wèn)題來(lái)了,在什么樣的場(chǎng)景下需要呢?里面寫(xiě)啥呢?
這個(gè)其實(shí)沒(méi)有唯一答案,每個(gè)人對(duì)理解鉤子的程度是不同的,導(dǎo)致有千千萬(wàn)萬(wàn)種類型的鉤子。
我個(gè)人更喜歡將它歸為處理臟活
的一類函數(shù),更通俗點(diǎn)來(lái)說(shuō),是用來(lái)處理響應(yīng)式數(shù)據(jù)
的,比如,有個(gè)獎(jiǎng)品業(yè)務(wù)的功能模塊,針對(duì)這個(gè)模塊,可能有驗(yàn)證獎(jiǎng)品配置的邏輯,那么我就會(huì)給這個(gè)獎(jiǎng)品加上幾個(gè) hook 函數(shù),以便后續(xù)方便調(diào)用。
const usePrize = () => {const verifyPrizeConfig = (state) => {// Do something ...}const resetPrizeConfig = (state) => {// Do something ...}return [verifyPrizeConfig,resetPrizeConfig,]
}
const [ verifyPrizeConfig ] = usePrize();
1.7 組件的生命周期
每個(gè)組件渲染時(shí),React 會(huì)逐步按順序觸發(fā)一些內(nèi)置函數(shù),這些被稱為”生命周期函數(shù)“,我們可以根據(jù)不同周期函數(shù)來(lái)做一些業(yè)務(wù)處理,比如我想在組件渲染前先請(qǐng)求接口得到數(shù)據(jù)。
這里需要注意,函數(shù)組件
是沒(méi)有命周期函數(shù)的,只有類組件
才有,既然這樣,我們將 App
這個(gè)組件變成類組件即可,其它保持不變,誰(shuí)規(guī)定類組件
里面就不能嵌套函數(shù)組件
?
- 第一步,將 App 函數(shù)組件改為類組件,如下:
// 源 App 函數(shù)組件
// const App = () => {
// return <div>Hello, world! <List/> </div>
// }
// 新 APP 類組件
class App extends React.Component {constructor(props) {super(props);}render() {return <div>Hello, world! <List /> </div>}
}
- 第二步,添加生命周期函數(shù),這里用最常見(jiàn)的
componentDidMount
函數(shù),表示組件第一次渲染時(shí)提前觸發(fā)。
class App extends React.Component {// 省略componentDidMount() {// Do something...alert('Init')}
}
1.8 函數(shù)組件模擬生命周期
你可能想,這不公平,函數(shù)組件憑啥沒(méi)有周期函數(shù)?別急,React 提供的鉤子函數(shù) useEffect
就派上用場(chǎng)了,該鉤子完全可以模擬生命周期的三大核心函數(shù):
componentDidMount() {} 組件第一次渲染時(shí),就剛剛用到的
componentDidUpdate() {} 組件數(shù)據(jù)更新時(shí)(state 更新)
componentWillUnMount() {} 組件銷毀時(shí)
使用方式也很簡(jiǎn)單,這里以 List 組件作為案例,不能忘記我們的老伙伴~
- 第一步,模擬
componentDidMount
,如下:
import { useState, useEffect } from 'react';
const List = () => {const [arr, setArr] = useState([1, 2, 3, 4]);// 模擬 componentDidMountuseEffect(() => {alert('List init') });return (<ul>{arr.map((num) => Item(num, setArr) )}</ul>)
}
- 第二步,模擬
componentDidUpdate
,在 useEffect 的第二個(gè)參數(shù)監(jiān)聽(tīng) state:
// ... 省略
const [arr, setArr] = useState([1, 2, 3, 4])useEffect(() => {alert('List init')}, [arr]) // componentDidUpdate
當(dāng)點(diǎn)擊更新數(shù)據(jù)時(shí),這個(gè)函數(shù)就會(huì)再次觸發(fā),如圖:
- 第三步,模擬
componentWillUnMount
,在 useEffect 里面 return 一個(gè)函數(shù)即可。
const [arr, setArr] = useState([1, 2, 3, 4])useEffect(() => {alert('List init')return () => { // componentWillUnMountalert('List destroyed!');};}, [arr])
componentWillUnMount 什么時(shí)候觸發(fā)呢?實(shí)際上,每當(dāng) state 更新時(shí),組件會(huì)重新渲染一次,這屬于銷毀行為,因此當(dāng)點(diǎn)擊更新數(shù)據(jù)后,會(huì)先觸發(fā) componentWillUnMount ,再觸發(fā) componentDidUpdate 。
效果:
三、React-router-dom
1.1 什么是 React-router-dom
一個(gè)頁(yè)面怎么夠用呢,現(xiàn)在我們想要通過(guò)點(diǎn)擊 item 進(jìn)入另一個(gè)頁(yè)面,且保持頁(yè)面不刷新,這里便可以用 React 提供的插件 React-router-dom ,俗稱路由
來(lái)實(shí)現(xiàn)。
1.2 React-router-dom 和 React-router 版本的區(qū)別
React-router-dom 是基于 React-router 改造的新版本,現(xiàn)在大家常用的版本是 React-router-dom
,
本案例將用 React-router-dom 來(lái)作為演示。
1.3 React-router-dom 的使用
- 第一步,下載 react-router-dom。
yarn add react-router-dom
- 第二步,在
src/index.js
中引入。
import {createBrowserRouter,RouterProvider,
} from "react-router-dom";
- 第三步,將我們之前的 App 組件引入方式重新改造一下
// 舊代碼
// root.render(<App />);
// 新代碼
const router = createBrowserRouter([{path: "/",element: <App/>,},
]);root.render(<RouterProvider router={router} />);
現(xiàn)在效果和原來(lái)沒(méi)區(qū)別,這里解釋下步驟:
- 將原來(lái) root.render 里的 App 替換成
RouterProvider
組件 - 在
createBrowserRoute
里面的element
引入 App。 - path (
路由
),當(dāng)訪問(wèn)/
根目錄時(shí)才會(huì)渲染 App 組件。
- 第四步,新建
AppDetail
組件并掛載到/detail
路由上。
const AppDetail = () => {return <h1>Detail data</h1>
}
const router = createBrowserRouter([{path: "/",element: <App/>,},// 掛載到 /detail 路由{path: "/detail",element: <AppDetail/>,},
]);
- 第五步,訪問(wèn)
/detail
看看效果
- 第六步,以點(diǎn)擊跳轉(zhuǎn)的方式訪問(wèn)
/detail
,這里用到 router 提供的<Link>
標(biāo)簽。
import {createBrowserRouter,RouterProvider,Link,
} from "react-router-dom";
const Item = (num, setArr) => {return (<li key={num} onClick={() => onClickItem(num, setArr)}><Link to='/detail'>item{num}</Link></li>)
}
效果:
react-router-dom 就這么簡(jiǎn)單,至于其它 API 的使用可以參考文檔,這里不過(guò)多講解。
四、React-redux
1.1 什么是 React-redux
React-redux 可以用來(lái)管理全局狀態(tài) state 的工具,使得組件之間可以訪問(wèn)同一份 state。
1.2 什么時(shí)候用 React-redux
當(dāng)多個(gè)組件重復(fù)使用同一份 state 時(shí)就可以考慮用 redux 將它提升到全局中,以便于維護(hù)。
1.3 React-redux 下載&配置
- 第一步,下載
react-redux
,這里官方還提供了一個(gè)工具包@reduxjs/toolkit
,里面包含了 react-redux 所有功能以及內(nèi)置一些其它功能,是官方極力推薦的,這兩個(gè)一起下載。
yarn add react-redux @reduxjs/toolkit
- 第二步,在 src 下新建
store/index.js
文件,負(fù)責(zé)管理全局 state,初始化內(nèi)容如下:
// ./src/store/index.js
import { configureStore, createSlice } from '@reduxjs/toolkit'
const userSlice = createSlice({name: 'User', // name 必填的,當(dāng)前作用域的標(biāo)識(shí)符,可以理解為 nameSpace 命名空間,否則頁(yè)面上無(wú)法正常展示。initialState: { // 聲明 state 的地方},reducers: { // 聲明 reducer 函數(shù)的地方}
});
// 將 store 導(dǎo)出去。
const store = configureStore({reducer: userSlice.reducer
})export default store;
- 第三步,在
src/index.js
中引入store
import store from './store/index';
import { Provider } from 'react-redux'
root.render(<Provider store={store}><RouterProvider router={router} /></Provider>
);
解釋:
- 從 store.js 中引入
store
對(duì)象。 - 從 redux 中引入
Provider
組件,并將原先的RouterProvider
組件包裹起來(lái)。 - 將
store
作為參數(shù)傳遞給Provider
組件。
目前來(lái)講,頁(yè)面跟原來(lái)沒(méi)有區(qū)別,但是現(xiàn)在我們多了 redux 的功能,何樂(lè)而不為呢。
1.4 什么是 Reducer
Reducer
與 Vuex 中的 mutations 差不多同一個(gè)意思,里面專門定義一些處理 state 的函數(shù),reducer 主要接受一個(gè) state 和一個(gè) action ,根據(jù)這兩個(gè)參數(shù)處理相關(guān)邏輯,然后返回新的 state (遵循前面所說(shuō)的“不可變?cè)瓌t”
)。
1.5 什么是 dispatch(action)
dispatch
是用來(lái)調(diào)用 reducer 函數(shù)的。action
是 dispatch 調(diào)用 reducer 函數(shù)時(shí)要傳遞的一個(gè)描述對(duì)象,好讓 reducer 知道該干什么事。該描述對(duì)象總共就倆參數(shù):type
/payload
,type 是調(diào)用 reducer 的函數(shù)名,payload 是我們要傳參的數(shù)據(jù),給 reducer 接受用的。
1.6 使用
理解 reducer/dispatch/action
三大核心概念之后,我們來(lái)開(kāi)始使用:
- 第一步,在
initialState
對(duì)象中定義全局響應(yīng)式數(shù)據(jù)。
// 省略...
initialState: {// 新增user: {name: 'Jack',desc: 'Hello,world!'}
},
- 第二步,新建
User
組件,該組件用來(lái)訪問(wèn)上面聲明的響應(yīng)式數(shù)據(jù),并掛載到 App 和 AppDetail 中渲染,代碼如下:
// ./src/index.js
// ...省略
import { Provider, useSelector } from 'react-redux'
// 新增
const User = () => {// 用 redux 提供的鉤子來(lái)獲取 stateconst user = useSelector(state => state.user); return (<div><span>{user.name}</span><span> says: {user.desc}</span></div>)
}
// ./src/index.js
// ...省略
class App extends React.Component {// ...省略render() {return (<div><User /> // 新增:掛載 User<List /> </div>)}
}
const AppDetail = () => {return (<h1>Detail data<User /> // 新增:掛載 User</h1>)
}
現(xiàn)在的效果:Jack says: Hello,world!
- 第三步,聲明
reducer
函數(shù)來(lái)變更 state 數(shù)據(jù)。
// .src/store/index.js
// ...省略
reducers: { // 聲明 reducer 函數(shù)的地方// 新增changeUserInfo(state, action) {const { payload } = action;switch(payload.state) {case 'name':return {...state,user: {...state.user,name: '杰克',}}case 'desc':return {...state,user: {...state.user,desc: '你好,世界!',}}default:return state;}}}
changeUserInfo 函數(shù)解釋:
- 根據(jù) payload.state 即我們準(zhǔn)備傳參的數(shù)據(jù)來(lái)變更用戶名 name 還是描述
desc - 遵循不破壞 state 原則,這里我們用擴(kuò)展符來(lái)合并。
- 第四步,使用
dispatch(action)
來(lái)觸發(fā) reducer,完成變更效果。
// ./src/index.js
import { Provider, useSelector, useDispatch } from 'react-redux'
const User = () => {const user = useSelector(state => state.user);const dispatch = useDispatch(); // 引入觸發(fā) reducer 的鉤子return (<div><span>{user.name}</span><span> says: {user.desc}</span>// 以下是新增的<button onClick={() => dispatch({type: 'User/changeUserInfo',payload: {state: 'name'}})}>更換名字</button><button onClick={() => dispatch({type: 'User/changeUserInfo',payload: {state: 'desc'}})}>更換描述</button></div>)
}
現(xiàn)在來(lái)看看總體效果:
五、總結(jié)
不知不覺(jué),我們已經(jīng)用到了 React-dom & React-router& React-redux:
root.render( // react-dom<Provider store={store}> // react-redux<RouterProvider router={router} /> // react-router-dom</Provider>
);
恭喜你已成功入門 React 全家桶,剩下就交給實(shí)踐的時(shí)間來(lái)幫助我們熟能生巧。
有問(wèn)題歡迎指出!
完!
案例已放在 github 上