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("");
}
};
// ...
}