Context參考
HOC參考
Hooks參考
組件跨層級通信 - Context
import React from 'react'
// 創建上下文
const Context = React.createContext()
// 獲取Provider和Consumer
const Provider = Context.Provider
const Consumer = Context.Consumer
// Child顯示計數器,並能修改它,多個Child之間需要共享數據
function Child(props) {
return <div onClick={() => props.add()}>{props.counter}</div>
}
export default class ContextTest extends React.Component {
// state是要傳遞的數據
state = {
counter: 0,
}
// add方法可以修改狀態
add = () => {
this.setState((nextState) => ({ counter: nextState.counter + 1 }))
}
// counter狀態變更
render() {
return (
<Provider value={{ counter: this.state.counter, add: this.add }}>
{/* Consumer中內嵌函數,其參數是傳遞的數據,返回要渲染的組件 */}
{/* 把value展開傳遞給Child */}
<Consumer>{(value) => <Child {...value} />}</Consumer>
<Consumer>{(value) => <Child {...value} />}</Consumer>
</Provider>
)
}
}
高階組件
// Hoc.js
import React from 'react'
// Lesson保證功能單一,它不關心數據來源,只負責顯示
function Lesson(props) {
return (
<div>
{props.stage} - {props.title}
</div>
)
}
// 模擬數據
const lessons = [
{ stage: 'React', title: '核心API' },
{ stage: 'React', title: '組件化1' },
{ stage: 'React', title: '組件化2' },
]
// 高階組件withContent負責包裝傳入組件Comp
// 包裝後組件能夠根據傳入索引獲取課程數據,真實案例中可以通過api查詢得到
const withContent = (Comp) => (props) => {
const content = lessons[props.idx]
// {...props}將屬性展開傳遞下去
return <Comp {...content} />
}
// LessonWithContent是包裝後的組件
const LessonWithContent = withContent(Lesson)
export default function HocTest() {
// HocTest渲染三個LessonWithContent組件
return (
<div>
{[0, 0, 0].map((item, idx) => (
<LessonWithContent idx={idx} key={idx} />
))}
</div>
)
}
鏈式調用
// withConsumer是高階組件工廠,它能根據配置返回一個高階組件
function withConsumer(Consumer) {
return (Comp) => (props) => {
return <Consumer>{(value) => <Comp {...value} {...props} />}</Consumer>
}
}
// Child顯示計數器,並能修改它,多個Child之間需要共享數據
// 新的Child是通過withConsumer(Consumer)返回的高階組件包裝所得
const Child = withConsumer(Consumer)(function (props) {
return (
<div onClick={() => props.add()} title={props.name}>
{props.counter}
</div>
)
})
export default class ContextTest extends React.Component {
render() {
return (
<Provider value={{ counter: this.state.counter, add: this.add }}>
{/* 改造過的Child可以自動從Consumer獲取值,直接用就好了 */}
<Child name="foo" />
<Child name="bar" />
</Provider>
)
}
}
// 高階組件withLog負責包裝傳入組件Comp
// 包裝後組件在掛載時可以輸出日誌記錄
const withLog = (Comp) => {
// 返回組件需要生命週期,因此聲明爲class組件
return class extends React.Component {
render() {
return <Comp {...this.props} />
}
componentDidMount() {
console.log('didMount', this.props)
}
}
}
// LessonWithContent是包裝後的組件
const LessonWithContent = withLog(withContent(Lesson))
裝飾器寫法
組件複合 - Composition
組件複合
// 裝飾器只能用在class上
// 執行順序從下往上
@withLog
@withContent
class Lesson2 extends React.Component {
render() {
return (
<div>
{this.props.stage} - {this.props.title}
</div>
)
}
}
export default function HocTest() {
// 這裏使用Lesson2
return (
<div>
{[0, 0, 0].map((item, idx) => (
<Lesson2 idx={idx} key={idx} />
))}
</div>
)
}
import React from 'react'
// Dialog定義組件外觀和行爲
function Dialog(props) {
return <div style={{ border: '1px solid blue' }}>{props.children}</div>
}
export default function Composition() {
return (
<div>
{/* 傳入顯示內容 */}
<Dialog>
<h1>組件複合</h1>
<p>複合組件給與你足夠的敏捷去定義自定義組件的外觀和行爲</p>
</Dialog>
</div>
)
}
import React from 'react'
// 獲取相應部分內容展示在指定位置
function Dialog(props) {
return (
<div style={{ border: '1px solid blue' }}>
{props.children.default}
<div>{props.children.footer}</div>
</div>
)
}
export default function Composition() {
return (
<div>
{/* 傳入顯示內容 */}
<Dialog>
{{
default: (
<>
<h1>組件複合</h1>
<p>複合組件給與你足夠的敏捷去定義自定義組件的外觀和行爲</p>
</>
),
footer: <button onClick={() => alert('react確實好')}>確定</button>,
}}
</Dialog>
</div>
)
}
function RadioGroup(props) {
// 不可行,
// React.Children.forEach(props.children, child => {
// child.props.name = props.name;
// });
return (
<div>
{React.Children.map(props.children, (child) => {
// 要修改child屬性必須先克隆它
return React.cloneElement(child, { name: props.name })
})}
</div>
)
}
Hooks
準備工作
狀態鉤子 State Hook
// Radio傳入value,name和children,注意區分
function Radio({ children, ...rest }) {
return (
<label>
<input type="radio" {...rest} />
{children}
</label>
)
}
export default function Composition() {
return (
<div>
{/* 執行顯示消息的key */}
<RadioGroup name="mvvm">
<Radio value="vue">vue</Radio>
<Radio value="react">react</Radio>
<Radio value="ng">angular</Radio>
</RadioGroup>
</div>
)
}
npm i react react-dom -S
import React, { useState } from "react";
export default function HooksTest() {
// useState(initialState),接收初始狀態,返回一個由狀態和其更新函數組成的數組
const [fruit, setFruit] = useState("");
return (
<div>
<p>{fruit === "" ? "請選擇喜愛的水果:" : `您的選擇是:${fruit}`}</p>
</div>
);
}
// 聲明列表組件
function FruitList({ fruits, onSetFruit }) {
return (
<ul>
{fruits.map((f) => (
<li key={f} onClick={() => onSetFruit(f)}>
{f}
</li>
))}
</ul>
)
}
export default function HooksTest() {
// 聲明數組狀態
const [fruits, setFruits] = useState(['香蕉', '西瓜'])
return (
<div>
{/*添加列表組件*/}
<FruitList fruits={fruits} onSetFruit={setFruit} />
</div>
)
}
用戶輸入處理
// 聲明輸入組件
function FruitAdd(props) {
// 輸入內容狀態及設置內容狀態的方法
const [pname, setPname] = useState('') // 鍵盤事件處理
const onAddFruit = (e) => {
if (e.key === 'Enter') {
props.onAddFruit(pname)
setPname('')
}
}
return (
<div>
{' '}
<input
type="text"
value={pname}
onChange={(e) => setPname(e.target.value)}
onKeyDown={onAddFruit}
/>{' '}
</div>
)
}
export default function HooksTest() {
// ...
return (
<div>
{' '}
{/*添加水果組件*/}
<FruitAdd onAddFruit={(pname) => setFruits([...fruits, pname])} />{' '}
</div>
)
}
import { useEffect } from 'react'
useEffect(() => {
setTimeout(() => {
setFruits(['香蕉', '西瓜'])
}, 1000)
}, []) // 設置空數組意爲沒有依賴,則副作用操作僅執行一次
useEffect(() => {
const timer = setInterval(() => {
console.log('msg')
}, 1000)
return function () {
clearInterval(timer)
}
}, [])
useReducer
useEffect(() => {
const timer = setInterval(() => {
console.log('msg');
}, 1000);
return function(){
clearInterval(timer);
}
}, []);
import { useReducer } from "react";
// 添加fruit狀態維護fruitReducer
function fruitReducer(state, action) {
switch (action.type) {
case "init":
return action.payload;
case "add":
return [...state, action.payload];
default:
return state;
}
}
export default function HooksTest() {
// 組件內的狀態不需要了
// const [fruits, setFruits] = useState([]);
// useReducer(reducer,initState)
const [fruits, dispatch] = useReducer(fruitReducer, []);
useEffect(() => {
setTimeout(() => {
// setFruits(["香蕉", "西瓜"]);
// 變更狀態,派發動作即可
dispatch({ type: "init", payload: ["香蕉", "西瓜"] });
}, 1000);
}, []);
return (
<div>
{/*此處修改爲派發動作*/}
<FruitAdd onAddFruit={pname => dispatch({type: 'add', payload: pname})} />
</div>
);
}
useContext
- 1. Hooks規則
- 2. 自定義Hooks
- 3. 一堆nb的實現
推薦練習
import React, { useContext } from "react";
// 創建上下文
const Context = React.createContext();
export default function HooksTest() {
// ...
return (
{/* 提供上下文的值 */}
<Context.Provider value={{fruits,dispatch}}>
<div>
{/* 這裏不再需要給FruitAdd傳遞狀態mutation函數,實現瞭解耦 */}
<FruitAdd />
</div>
</Context.Provider>
);
}
function FruitAdd(props) {
// 使用useContext獲取上下文
const {dispatch} = useContext(Context)
const onAddFruit = e => {
if (e.key === "Enter") {
// 直接派發動作修改狀態
dispatch({ type: "add", payload: pname })
setPname("");
}
};
// ...
}