1、React表單控制
1.1 受控綁定
概念:使用React組件的狀態(useState)控制表單的狀態
完整示例:
function App(){/* 1. 準備一個React狀態值 */ const [value, setValue] = useState('')return (/* 2. 通過value屬性綁定狀態,通過onChange屬性綁定狀態同步的函數*/<input type="text" value={value} onChange={e => setValue(e.target.value)}/>)
}
1.2 非受控綁定
概念:通過獲取DOM的方式獲取表單的輸入數據
- 使用useRef創建 ref 對象,并與 JSX 綁定
const inputRef = useRef(null)
<input type="text" ref={inputRef} />
- 在DOM可用時,通過 inputRef.current 拿到 DOM 對象
console.log(inputRef.current)
示例:
// 渲染完畢之后dom才可用
function App() {const inputRef = useRef(null)const showDom = () => {// console.log(inputRef.current);console.dir(inputRef.current); }return (<div className="App"><input type="text" ref={inputRef} /><button onClick={showDom}>點擊</button></div>)
}
2、案例-B站評論案例
- 輸入框評論內容,并發布評論
核心代碼:
const App = () => {// 使用useState維護評論列表const [commentList, setCommentList] = useState(_.orderBy(defaultList,'like','desc'))// 1. 獲取評論內容const[content, setContent] = useState('')// 2.點擊發布評論內容const handPublish=()=>{// 在原始的評論列表中添加新增的setCommentList(...commentList,{rpid: 100,user: {uid: '30009257',avatar,uname: '學習前端',},content: content,ctime: '10-19 09:00',like: 66,})}return (<div className="app"><div className="reply-box-wrap">{/* 評論框 */}<textareaclassName="reply-box-textarea"placeholder="發一條友善的評論"value={content}onChange={(e)=>setContent(e.target.value)}/>{/* 發布按鈕 */}<div className="reply-box-send"><div className="send-text" onClick={handPublish}>發布</div></div></div></div>}
- id處理和時間處理(uuid 和 day.js),使用方法看官方文檔(安裝—>導入–>使用)
核心代碼:
// 1. 導入生成隨機id的插件
import { v4 as uuid4 } from "uuid";
// 2.導入時間格式化插件
import dayjs from "dayjs";const handPublish=()=>{// 在原始的評論列表中添加新增的setCommentList(...commentList,{rpid: uuid4(),user: {uid: '30009257',avatar,uname: '黑馬前端',},content: content,ctime: dayjs(new Date()).format('MM-DD HH:mm'), //時間格式化為 ’月-日 時:分‘like: 66,})}
- 清空內容并重新聚焦
const textRef = useRef(null)//1. 清空內容 - 把控制input框的value狀態設置為空串setContent('')// 2. 重新聚焦 - 拿到input的dom元素,調用focus方法textRef.current.focus(){/*評論區*/}<textareaclassName="reply-box-textarea"placeholder="發一條友善的評論"value={content}ref={textRef} //綁定refonChange={(e)=>setContent(e.target.value)}/>
3、React組件通信
概念:組件通信就是
組件之間的數據傳遞
, 根據組件嵌套關系的不同,有不同的通信手段和方法
- A-B 父子通信
- B-C 兄弟通信
- A-E 跨層通信
4、父子通信-父傳子
4.1 基礎實現
**實現步驟 **
- 父組件傳遞數據 - 在子組件標簽上綁定屬性
- 子組件接收數據 - 子組件通過props參數接收數據
// 子組件
function Son(props) {// console.log(props); // {name: '這是父組件的內容,將其綁定到子組件'}return <div>this is son,{props.name}</div>
}function App() {// 父組件定義變量const name = '這是父組件的內容,將其綁定到子組件'return (<div className="App"><Son name={name} /></div>)
}
4.2 props說明
props可以傳遞任意的合法數據,比如數字、字符串、布爾值、數組、對象、函數、JSX
需要使用什么,就在子組件里調用{props. } ,比如{props.age}
props是只讀對象
子組件只能讀取props中的數據
,不能直接進行修改, 父組件的數據只能由父組件修改
4.3 特殊的prop-chilren
場景:當我們把內容嵌套在組件的標簽內部時,組件會自動在名為children的prop屬性中接收該內容
function Son(props) {console.log(props);//使用return <div>this is son,{ props.children}</div>
}function App() {return (<div className="App"><Son>// 傳入特殊的prop-chilren<span>this is span</span></Son></div>)
}
5、父子通信-子傳父
核心思路:在子組件中調用父組件中的函數并傳遞參數
function Son({ongetMsg}) {const sonMsg = 'this is son msg'return <div>this is son// 傳遞參數<button onClick={()=>{ongetMsg(sonMsg)}}>點擊</button></div>
}function App() {const [msgs, setMsgs] = useState('')const getMsg = (msg) => {console.log(msg);setMsgs(msg)}return (<div className="App">{msgs}// 在子組件中傳遞父組件的函數<Son ongetMsg={ getMsg } /></div>)
}
6、兄弟組件通信
實現思路: 借助
狀態提升
機制,通過共同的父組件進行兄弟之間的數據傳遞
- A組件先通過子傳父的方式把數據傳遞給父組件App
- App拿到數據之后通過父傳子的方式再傳遞給B組件
子傳父–>父傳子
// 1. 通過子傳父 A -> App
// 2. 通過父傳子 App -> B
import { useState } from "react"function A({onGetname}) {const aname = 'this is A name'return <div>this is A component,{/* 子傳父,傳入參數 */}<button onClick={()=>onGetname(aname)}>send</button></div>
}
function B(props) {// 父傳子,接收傳遞的數據return <div>this is B component,{props.name}</div>
}function App() {const[name, setName] = useState('')const getName = (fname)=> {console.log(fname);setName(fname)}return (<div className="App">this is app,{/* 子傳父 ,定義函數*/}<A onGetname={getName} />{/* 父傳子 */}<B name={name}/></div>)
}export default App
7、跨層組件通信
使我們App里得數據,跨過A直接傳遞給B
實現步驟:
- 使用
createContext
方法創建一個上下文對象Ctx(這個名字可以隨便取) - 在頂層組件(指App)中通過
Ctx.Provider
組件提供數據 - 在底層組件(B)中通過
useContext
鉤子函數獲取消費數據
// App -> A -> Bimport { createContext, useContext } from "react"// 1. createContext方法創建一個上下文對象const MsgContext = createContext()function A () {return (<div>this is A component<B /></div>)
}function B () {// 3. 在底層組件 通過useContext鉤子函數使用數據const msg = useContext(MsgContext)return (<div>this is B compnent,{msg}</div>)
}function App () {const msg = 'this is app msg'return (<div>{/* 2. 在頂層組件 通過Provider組件提供數據 */}<MsgContext.Provider value={msg}>this is App<A /></MsgContext.Provider></div>)
}export default App
8、React副作用管理-useEffect
8.1 概念理解
useEffect是一個React Hook函數,用于在React組件中創建不是由事件引起而是由渲染本身引起的操作(副作用), 比 如發送AJAX請求,更改DOM等等
說明:上面的組件中沒有發生任何的用戶事件,
組件渲染完畢之后
就需要和服務器要數據,整個過程屬于“只由渲染引起的操作”
8.2 基礎使用
需求:在組件渲染完畢之后,立刻從服務端獲取平道列表數據并顯示到頁面中
說明:
- 參數1是一個函數,可以把它叫做副作用函數,在函數內部可以
放置要執行的操作
- 參數2是一個數組(可選參),在數組里放置依賴項,不同依賴項會影響第一個參數函數的執行,當是一個空數組的時候,副作用函數只會在組件渲染完畢之后執行一次
接口地址:http://geek.itheima.net/v1_0/channels
import { useEffect,useState } from "react";function App() {const[list, setList]=useState([])const URL = 'http://geek.itheima.net/v1_0/channels'useEffect(() => {// 放置要執行的操作async function getlist() {const res = await fetch(URL)// console.log(res);// 轉json字符串const jsonList = await res.json()console.log(jsonList);setList(jsonList.data.channels)}getlist()},[])return (<div className="App">this is app<ul>{list.map(item=><li key={item.id}>{item.name}</li>)}</ul></div>)
}
8.3 useEffect依賴說明
useEffect副作用函數的執行時機存在多種情況,根據傳入依賴項的不同,會有不同的執行表現
依賴項 | 副作用功函數的執行時機 |
---|---|
沒有依賴項 | 組件初始渲染 + 組件更新時執行 |
空數組依賴 | 只在初始渲染時執行一次 |
添加特定依賴項 | 組件初始渲染 + 依賴項變化時執行 |
8.4 清除副作用
概念:在useEffect中編寫的由渲染本身引起的對接組件外部的操作,社區也經常把它叫做副作用操作,比如在useEffect中開啟了一個定時器,我們想在組件卸載時把這個定時器再清理掉,這個過程就是清理副作用
說明:清除副作用的函數最常見的執行時機是在組件卸載時自動執行
import { useEffect, useState } from "react"function Son () {// 1. 渲染時開啟一個定時器useEffect(() => {const timer = setInterval(() => {console.log('定時器執行中...')}, 1000)return () => {// 清除副作用(組件卸載時)clearInterval(timer)}}, [])return <div>this is son</div>
}function App () {// 通過條件渲染模擬組件卸載const [show, setShow] = useState(true)return (<div>{show && <Son />}<button onClick={() => setShow(false)}>卸載Son組件</button></div>)
}export default App
?????
此處沒弄明白:
為什么沒在第一次運行的時候就執行return?
9、自定義Hook實現
概念:自定義Hook是以
use打頭的函數
,通過自定義Hook函數可以用來實現邏輯的封裝和復用
封裝自定義hook通用思路
- 聲明一個以use打頭的函數
- 在函數體內封裝可復用的邏輯(只要是可復用的邏輯)
- 把組件中用到的狀態或者回調return出去(以對象或者數組)
- 在哪個組件中要用到這個邏輯,就執行這個函數,解構出來狀態和回調進行使用
// 封裝自定義Hook// 問題: 布爾切換的邏輯 當前組件耦合在一起的 不方便復用// 解決思路: 自定義hookimport { useState } from "react"function useToggle () {// 可復用的邏輯代碼const [value, setValue] = useState(true)const toggle = () => setValue(!value)// 哪些狀態和回調函數需要在其他組件中使用 returnreturn {value,toggle}
}function App () {const { value, toggle } = useToggle()return (<div>{value && <div>this is div</div>}<button onClick={toggle}>toggle</button></div>)
}export default App
10、React Hooks使用規則
- 只能在組件中或者其他自定義Hook函數中調用
- 只能在組件的頂層調用,不能嵌套在if、for、其它的函數中
11、案例-優化B站評論案例
- 使用請求接口的方式獲取評論列表并渲染
- 使用自定義Hook函數封裝數據請求的邏輯
- 把評論中的每一項抽象成一個獨立的組件實現渲染
準備工作:
-
使用 json-server (訪問json-server 的 github官網進行安裝、準備db.json文件)工具模擬接口服務,安裝好后,在package.json文件下添加:
執行:npm ren serve
返回的接口鏈接,蓋鏈接就是下面axios需要使用的接口鏈接 -
通過 axios 發送接口請求,axios是一個廣泛使用的前端請求庫(安裝axios:
npm install axios
) -
使用 useEffect 調用接口獲取數據
實現:
1.使用請求接口的方式獲取評論列表并渲染
2.使用自定義Hook函數封裝數據請求的邏輯
3. 把評論中的每一項抽象成一個獨立的組件實現渲染
核心技術:
抽離評論列表
通過父傳子,將父組件(App)的方法傳遞給子組件(Item)——>然后通過子傳父,將子組件的item.rpid傳遞給父組件