前言
受控组件有俩种不同的定义:
- 狭义上我们关注于表单的受控与非受控
- 而广义上我们将面向全局探讨受控与非受控,也就是React组件的渲染是否被调用者传递的
props
完全控制,控制则为受控组件,否则是非受控组件
表单中的非受控组件
import React, { Component } from 'react';
export default class App extends Component {
loginRef = React.createRef();
render() {
return (
<section>
<h1>表单中的非受控组件</h1>
<form action="#">
{/* 非受控组件行为 */}
{/* <input type="text" ref={this.loginRef} value="Hello" /> */}
{/* 因为这是JSX文本,不是原本的DOM文档,所以这个value值相当于传给input标签一个value值,写死的值 */}
<input type="text" ref={this.loginRef} defaultValue="Hello" />
{/* defaultValue代表只是代表第一次初始化value值,不去控制后续的更新, 脱离了react的控制 ,直接通过组件的原生DOM节点获取设置value值*/}
<br />
<button
onClick={() => {
console.log(this.loginRef.current.value);
}}
>
Login
</button>
<button
onClick={() => {
this.loginRef.current.value = '';
}}
>
重置
</button>
</form>
</section>
);
}
}
// 什么是非受控组件?
/* React要编写一个非受控组件,可以使用ref来从DOM节点中获取表单数据,就是非受控组件 */
这是一个我已经写完的组件了(从受控表单组件到非受控表单组件的演变),并且之前的 ’错误‘ 注释我并没有删除,我们来看看最初时我们是怎么写这个表单的,我们其实就是想给表单设置一个初始默认值value,原生这样写是完全没有问题的,但是放到 React 中我们发现如果你使用 value
属性来定义默认初始值的话会产生一个奇妙的严重的问题:用户根本无法更改你定义的初始值,这是为什么呢? 🤔
其实这离不开 JSX 语法,我们在react组件中写的标签是 JSX 语法,不能直接渲染到页面上,那么按照这个思路来说,只能有一种可能,React为我们做了这一步,那么我们定义的这个value值就是 React 内部帮我们定义的一个死值,所以这就导致了我们无法更改的受控组件(我们也可以把JSX看成一个组件,毕竟是React内部帮我们转换的)
解决这个问题的方法当然有了,我在上面的注释中已经写的很清楚了,我们可以使用 react 为我们提供的属性:
defaultValue
:defaultValue代表只是代表第一次初始化value值,不去控制后续的更新, 脱离了react的控制 ,直接通过组件的原生DOM节点获取设置value值,相当于第一次初始化值之后不受React的控制,而是通过拿到原生得DOM节点来设置value值
受控组件
上面的方法固然很好,但是我们想让这个属性值在子组件中也可以使用,那么这就受到了一个约束性,defaultValue
的属性值只能在当前组件中使用,我们一定会萌发出这样一个想法:可不可以让input表单受控于状态呢?我们试试:
import React, { Component } from 'react';
export default class App extends Component {
constructor() {
super();
this.state = {
user_name: 'Anna',
};
}
render() {
return (
<section>
<h1>表单中的受控组件</h1>
<form action="#">
<input
type="text"
ref={this.loginRef}
value={this.state.user_name}
// onInput={() => {
// this.setState({
// user_name: 0,
// });
// }}
onChange={e => {
// 每次输入框中的值发生改变之后,给到状态 --> render 重新渲染
// input【组件】受控于状态每次更新的值
this.setState({
user_name: e.target.value,
});
}}
// 在react中 -- oninput和onchange的行为是一样的
// 在原生中onchange只在表单失去焦点触发一次
/>
<br />
<button
onClick={() => {
console.log(this.state.user_name);
}}
>
Login
</button>
<button
onClick={() => {
this.setState({
user_name: '',
});
}}
>
重置
</button>
</form>
</section>
);
}
}
受控组件实践妙用(真实请求Ajax - 模糊查询)
import React, { Component } from 'react';
import axios from 'axios';
import './CSS/HOME.css';
// 模糊查询
export default class App extends Component {
constructor() {
super();
this.state = {
message: [],
text: '搜索',
};
axios({
url: 'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=9604127',
method: 'get',
headers: {
'X-Client-Info':
'{ "a": "3000", "ch": "1002", "v": "5.2.0", "e": "16595792052270847993643009","bc": "110100"}',
'X-Host': 'mall.film-ticket.film.list',
},
})
.then(res => {
this.setState({
message: res.data.data.films,
});
console.log(res.data.data.films);
})
.catch(err => console.log(err));
}
render() {
return (
<section>
<input
type="text"
className="search_ipt"
value={this.state.text}
onChange={e => this.searchFunc(e)}
/>
<ul className="moviceUl">
{this.getCinemaList().map(list => (
<li key={list.filmId}>
<div>
<h3>{list.name}</h3>
<em>{list.synopsis}</em>
<img src={list.poster} alt={list.name} />
</div>
</li>
))}
</ul>
</section>
);
}
getCinemaList = () =>
this.state.message.filter(
list =>
list.name.trim().includes(this.state.text.trim()) ||
list.synopsis.trim().includes(this.state.text.trim())
);
searchFunc = e => {
this.setState({
text: e.target.value,
});
};
}
广义上的受控组件
父传子与子传父之间的通信
无论是 React 还是 Vue等等这些框架,都具有相同点,那就是组件!组件!组件!复用!复用!复用! 😆
那么这就必然会用到组件之间的通信,也就是信息的传递,Vue的通信使用自定义事件,使用EventBus,那么 React 中怎么实现父子,兄弟传递数据呢?
import React, { Component } from 'react';
// 子组件 -- Navbar
class Navbar extends Component {
render() {
return (
<section className="navbar" style={{ backgroundColor: 'red' }}>
<button>Click</button>
<span>Navbar</span>
</section>
);
}
}
// 子组件 -- Sidebar
class Sidebar extends Component {
render() {
return (
<section className="sidebar" style={{ backgroundColor: 'green' }}>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
</section>
);
}
}
export default class App extends Component {
render() {
return (
<section>
<Navbar></Navbar>
<Sidebar></Sidebar>
</section>
);
}
}
现在我们想要点击按钮让子组件Sidebar 消失,肯定不能使用状态呀,各个组件的状态是不共享,不相通的,所以我们想到了能不能使用属性,将子组件的信号给到父组件,然后父组件做出相应的动作,当然状态肯定是必不可少的,我们可以使用属性传递过来的信息改变状态达到改变UI结构的目的
// 首先给到一个父组件状态,使状态能够控制子组件的隐藏或者显示
// 首先我们思考一个问题,子组件点击事件改变父组件状态,并且父组件需要修改状态,那么这个事件应该写在哪里,应该怎么写?
export default class App extends Component {
constructor() {
super();
this.state = {
show: true,
};
}
render() {
return (
<section>
<Navbar
sidebarShow={() => this.setState({ show: !this.state.show })}
></Navbar>
{this.state.show && <Sidebar></Sidebar>}
</section>
);
}
}
没错,我们在父组件上定义一个回调函数,这样的话,将回调函数传给我们的子组件,子组件接收到该回调函数体,通过点击事件执行该回调,这个思路不错,我们立马试试
// 子组件 -- Navbar
class Navbar extends Component {
render() {
return (
<section className="navbar" style={{ backgroundColor: 'red' }}>
<button
onClick={() => {
this.props.sidebarShow();
}}
>
Click
</button>
<span>Navbar</span>
</section>
);
}
}
Perfect!!! 😀
我们来总结一下子传父以及父传子的方法:
- 父传子:自定义属性
- 子传父:父组件中给到子组件回调(以属性的方式父传子给子组件),子组件接收到props下的回调并在执行过程中将子组件数据通过参数传递给父组件使用
回到正题,我们说的是受控组件,无论是子传父还是父传子,都只是为了这个受控组件做基础铺垫的,我们不想让子组件拥有自己的封闭状态,它应该是一个可控的,而不是一个封闭的,也就是我们所说的无状态组件,只要通过这样的传值就可以实现子组件的无状态性,所有需要根据需求更改的数据都由父组件来操作使用属性进行传递数据,重新渲染