组件之间就像发动机的各个零件,想要让 React 这台机器运作起来,就要处理好各个零件,也就是各个组件之间的联系,而 props
担任的角色就是将每个组件联系起来。
props
是 React 组件通信最重要的手段,它在 React 的世界中充当的角色是十分重要的。
props
是什么对于在 React 应用中写的子组件,无论是函数组件 FunComponent
,还是类组件 ClassComponent
,父组件绑定在它们标签里的属性/方法,最终会变成 props
传递给它们。对于一些特殊的属性,比如说 ref
或者 key
,React 会在底层做一些额外的处理。
function ChildrenComponent() {
return <div>ChildrenComponent</div>;
}
class PropsComponent extends React.Component {
componentDidMount() {
console.log('_this', this);
}
render () {
const { children, mes, renderName, say, Component } = this.props;
const renderFunction = children[0];
const renderComponent = children[1];
return <div>
{ renderFunction() }
{ mes }
{ renderName() }
{ renderComponent }
<Component />
<button onClick={() => say()}>change content</button>
</div>
}
}
class Index extends React.Component {
state = {
mes: 'hello world',
}
node = null
say = () => {
this.setState({
mes: 'hello react',
})
}
render() {
return <div>
<PropsComponent
mes={this.state.mes}
say={this.say}
Component={ChildrenComponent}
renderName={() => {
return <div>renderName</div>
}}
>
{() => {
return <div>renderFunction</div>
}}
<ChildrenComponent />
</PropsComponent>
</div>
}
}
props
可以作为:
mes
say
ChildrenComponent
renderName
renderFunction
renderComponent
PropsComponent
如果是一个类组件,那么可以直接通过 this.props
访问到它:
在标签内部的属性和方法会直接绑定在 props
对象的属性上,对于组件的插槽会被绑定在 props
的 children
属性中。
props
props
能做的事情:
props
充当的角色 props
可以把数据层传递给子组件去渲染消费props
中的 callback
,来向父组件传递信息props
进行渲染props
充当的角色 props
在组件更新中充当了重要的角色,在 fiber
调和阶段中,diff
可以说是 React 更新的驱动器props
可以作为组件是否更新的重要准则,变化即更新,于是有了 PureComponent
,memo
等性能优化方案props
充当的角色 children
属性props
变化类组件
componentWillReceiveProps
可以作为监听 props
的生命周期,但是 React 已经不推荐使用 componentWillReceiveProps
,因为这个生命周期超越了 React 的可控制的范围内,可能引起多次执行等情况发生getDerivedStateFromProps
函数组件
useEffect
来作为 props
改变后的监听函数(有一点值得注意, useEffect
初始化会默认执行一次)function Index(props) {
useEffect(() => {
console.log('props.number change', props.number)
}, [props.number])
return <div>Index</div>
}
props children
模式props
+ children
模式 在 React 中非常常用,尤其对一些优秀开源组件库。比如 react-router 中的 Switch
和 Route
, antd 中的 Form
和 FormItem
。
props
+ children
的基本应用:
props
插槽组件
<Container>
<Children></Children>
</Container>
在 Container
组件中,通过 props.children
属性访问到 Children
组件,为 React element 对象,作用:
Children
组件的渲染Container
可以用 React.cloneElement
强化 props
或者修改 Children
的子元素render props
模式
<Container>
{(ContainerProps) => <Children {...ContainerProps} />}
</Container>
在 Container
组件中,通过 props.children
属性访问到 Children
为一个函数,作用:
Children
组件的渲染Children
的 props
直接通过函数参数的方式传递给执行函数 children
混合模式
Container
的 Children
既有函数也有组件<Container>
{(ContainerProps) => <Children {...ContainerProps} />}
<Children />
</Container>
children
,判断 children
元素类型: element
节点,通过 cloneElement
混入 props
props
使用小技巧抽象 props
props
一般用于跨层级传递 props
,一般不需要具体指出 props
中某个属性,而是将 props
直接传入或者是抽离到子组件中混入 props
function Child(props) {
console.log(props)
return <div>hello, world</div>
}
function Parent(props) {
const parentProps = {
msg: 'Let us learn React'
}
return <Child {...props} {...parentProps} />
}
function Index() {
const indexProps = {
name: 'cell',
age: 18
}
return <Parent {...indexProps} />
}
抽离 props
function Child(props) {
console.log(props)
return <div>hello, world</div>
}
function Parent(props) {
const { msg, ...childProps } = props
return <Child {...childProps} />
}
function Index() {
const indexProps = {
name: 'cell',
age: 18,
msg: 'Let us learn React'
}
return <Parent {...indexProps} />
}
注入 props
显式注入 props
:能够直观看见标签中绑定的 props
function Child(props) {
console.log(props)
return <div>hello, world</div>
}
function Parent(props) {
return props.children
}
function Index() {
return <Parent>
<Child name="cell" age={18} />
</Parent>
}
隐式注入 props
:一般通过 React.cloneElement 对 props.children
克隆再混入新的 props
function Child(props) {
console.log(props)
return <div>hello, world</div>
}
function Parent(props) {
return React.cloneElement(props.children, {
msg: 'Let us learn React'
})
}
function Index() {
return <Parent>
<Child name="cell" age={18} />
</Parent>
}
实现一个 Demo ,用于表单状态管理的 <Form>
和 <FormItem>
组件:
<Form>
用于管理表单状态<FormItem>
用于管理 <Input>
输入框组件组件需要实现的功能:
Form
组件可以被 ref
获取实例 submitForm
获取表单内容,用于提交表单resetForm
方法用于重置表单Form
组件自动过滤掉除了 FormItem
之外的其他 React 元素FormItem
中 name
属性作为表单提交时候的 key
,还有展示的 label
FormItem
可以自动收集 <Input/>
表单的值<Form>
class Form extends React.Component {
state = {
formData: {}
}
submitForm = (cb) => {
cb({...this.state.formData})
}
resetForm = () => {
const { formData } = this.state;
Object.keys(formData).forEach(key => {
formData[key] = '';
});
this.setState({
formData
});
}
setValue = (name, value) => {
this.setState({
formData: {
...this.state.formData,
[name]: value
}
});
}
render() {
const { children } = this.props;
const renderChildren = [];
React.Children.forEach(children, (child) => {
if (child.type.displayName === 'formItem') {
const { name } = child.props;
const Children = React.cloneElement(child, {
key: name,
handleChange: this.setValue,
value: this.state.formData[name] || ''
}, child.props.children);
renderChildren.push(Children);
}
});
return renderChildren;
}
}
Form.displayName = 'form';
设计思路:
<Form>
在不使用 forwardRef
前提下,最好是类组件,因为只有类组件才能获取实例state
下的 formData
属性,用于收集表单状态FormItem
元素之外的其他元素 props.children
的时候就可以在 React element 的 type
属性(类或函数组件本身)上,验证这个身份FormItem
节点,将改变表单单元项的方法 handleChange
和表单的值 value
混入 props
中<FormItem>
function FormItem(props) {
const { children, name, handleChange, value, label } = props;
const onChange = (value) => {
handleChange(name, value);
};
return <div className='form'>
<span className='label'>{label}:</span>
{
React.isValidElement(children) && children.type.displayName === 'input'
? React.cloneElement(children, {
value,
onChange
})
: null
}
</div>;
}
FormItem.displayName = 'formItem';
设计思路:
FormItem
一定要绑定 displayName
属性,用于让 <Form>
识别 <FormItem />
onChange
方法,通过 props
提供给 <Input>
,作为改变 value
的回调函数FormItem
过滤掉除了 input
以外的其他元素<Input>
function Input({ onChange, value }) {
return <input
className='input'
onChange={(e) => {
onChange && onChange(e.target.value);
}}
value={value}
/>;
}
Input.displayName = 'input';
设计思路:
displayName
标识 input
input
DOM 元素,绑定 onChange
方法,用于传递 value
export default () => {
const form = React.useRef(null);
const submit = () => {
form.current.submitForm((formValue) => {
console.log(formValue);
});
};
const reset = () => {
form.current.resetForm();
};
return <div>
<Form ref={form}>
<FormItem name='name' label='姓名'>
<Input />
</FormItem>
<FormItem name='age' label='年龄'>
<Input />
</FormItem>
</Form>
<button onClick={submit}>提交</button>
<button onClick={reset}>重置</button>
</div>;
}