在 ✓ 🇨🇳 开篇:通过 state 阐述 React 渲染 中,以 setInterval
为例,梳理了 React 渲染的相关内容。
📢 本篇会 ✓ 🇨🇳 围绕 props 阐述 React 通信
props
React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它。
<Avatar
name="ligang"
address={<span>山东省</span>}
size={100}
/>
也可以拆分组件,将子组件作为 JSX 传递。
将 JSX 作为子组件传递
<Avatar
name="ligang"
size={100}>
<span>山东省</span>
</Avatar>
上述 Avatar
组件将接收一个被设为 <span>
的 children
prop 。
function Card({ children }) {
return (
<>{children}</>
);
}
注意! 需要区分
children
和Children
‼️ 在 React 中,children
属性是被视为 不透明的 数据结构。这意味着你不应该依赖它的结构。如果要转换,过滤,或者统计子节点,你应该使用 Children
方法。
实际操作过程中,children
在底层常常被表示为数组。但是如果这里只有一个子节点,那么 React 将不会创建数组,因为这将导致不必要的内存开销。 只要你使用 Children
方法而不是直接操作 children
底层结构,即使 React 改变了 children
数据结构的实际实现方式,你的代码也不会被中断。
语法 | 含义 |
---|---|
Children.count(children) | 可以获取 children 中的节点数量 |
Children.forEach(children, (child, index) => {}); | 为每个 children 中的每个子节点执行一段代码 |
Children.map(children, child => {}, thisArg?) | 对 children 中的每个子节点进行映射或转换 |
Children.only(children) | 断言 children 代表一个 React 元素 |
Children.toArray(children) | 通过 children 创建一个数组 |
☔️ Children
使得错误排查变得较为困难,推荐使用 替代方案1 而不是使用 Children
。
组件是否由 props 驱动,可以分为受控&非受控组件。
受控&非受控
当组件中的重要信息是由 props
而不是其自身状态驱动时,就可以认为该组件是 “受控组件”;受控组件具有最大的灵活性,但它们需要父组件使用 props
对其进行配置。
export default function Input ({value, onChange}) {
return (
<input
value={value}
onChange={e => {onChange(e.target.value)}}
/>
)
}
当组件中的重要信息是由其自身状态 state
驱动时,就可以认为该组件是 “非受控组件”;非受控组件通常很简单,因为它们不需要太多配置。
export default function Input () {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={e => {setValue(e.target.value)}}
/>
)
}
♠︎♠︎ 当编写一个组件时,你应该考虑哪些信息应该受控制(通过 props
),哪些信息不应该受控制(通过 state
)。
业务开发中,组件是受控或者非受控是明确的。但组件库中(如antd)有非常多的场景需要既支持受控模式又支持非受控模块(如input) <= 组件的状态既可以自己管理,也可以被外部控制。
推荐查看 ahooks useControllableValue2
‼️区分:纯函数
- 只负责自己的任务。它不会更改在该函数调用前就已存在的对象或变量。
- 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果。
不更改在该函数调用前就已存在的对象或变量 => 对于 props 同样至关重要!
将 props 视为只读
🧶 探讨:不要在 state 中镜像 props
父组件
import {useState} from 'react';
import Message from './Message.tsx';
export default function Hello () {
const [message, setMessage] = useState('world');
return (
<>
<input type="text" value={message} onChange={(e) => setMessage(e.target.value)}/>
<Message message={message}></Message>
</>
)
}
子组件
import {useState} from 'react';
export default ({message}: {message: string}) => {
const [msg, setMsg] = useState(message);
return <div>hello {msg}</div>
}
‼️这里,一个 msg
state 变量被初始化为 message
的 prop 值。这段代码的问题在于,如果父组件稍后传递不同的 message
值(例如,将其从 'world'
更改为 'ligang'
),则 msg
state 变量将不会更新! state 仅在第一次渲染期间初始化。
这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 message
属性。
💯 如果你想给它起一个更短的名称,请使用常量:
export default ({message}: {message: string}) => {
const msg = message;
这种写法就不会与从父组件传递的属性失去同步。
🔛只有当你 想要 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。
按照惯例,prop 名称以 initial
或 default
开头,以阐明该 prop 的新值将被忽略:
export default ({initialMessage}: {initialMessage: string}) => {
// 这个 `message` state 变量用于保存 `initialMessage` 的 **初始值**。
// 对于 `initialMessage` 属性的进一步更改将被忽略。
const [msg, setMsg] = useState(initialMessage);
https://react.docschina.org/reference/react/Children#alternatives Children替代方案 ↩︎
https://ahooks.js.org/zh-CN/hooks/use-controllable-value#usecontrollablevalue usecontrollablevalue ↩︎