表单中的非受控组件
设置默认值,使用ref来从 DOM 节点中获取表单数据,defaultValue设置默认值,不去控制后续的更新
import React, { Component } from 'react';
class App extends Component {
myUsername = React.createRef()
handleReset = () => {
if (this.myUsername.current) {
this.myUsername.current.value = ''; // 直接操作 DOM 元素的值,不能在onClick里修改,因为ref是只读的
}
};
render() {
return (
<div>
<h1>登录页</h1>
<input type="text" ref={this.myUsername} defaultValue={'孩子们我不想上学'}/>
<button onClick={()=>{console.log(this.myUsername.current.value)
}}>登录</button>
<button onClick={()=>this.handleReset()}>重置</button>
</div>
);
}
}
export default App;

表单的受控组件
受控组件其实就是把ref的值设为一个状态,input在React里其实更像一个组件,而不是一个普通的input标签
import React, { Component } from 'react';
class App extends Component {
myUsername = React.createRef()
handleReset = () => {
if (this.myUsername.current) {
this.myUsername.current.value = ''; // 直接操作 DOM 元素的值,不能在onClick里修改,因为ref是只读的
}
}
state = {
usename:'august'
}
render() {
return (
<div>
<h1>登录页</h1>
<input type="text" ref={this.myUsername} defaultValue={this.state.usename}/>
<button onClick={()=>{console.log(this.myUsername.current.value)
}}>登录</button>
<button onClick={()=>this.handleReset()}>重置</button>
</div>
);
}
}
export default App;
但是这时候没有设置onChange事件,通过onChange监听input,需要在input察觉到值变化以后立即更新状态
这种行为和原生js里的onInput事件更相似
对于 <input type="text">
和 <textarea>
,onChange
在元素失去焦点(blur
)时触发。
受控组件的优点是实现了父子通信,在重新渲染的同时,还可以把更改的状态传给其他组件
import React from 'react'
import axios from 'axios'
class Cinema extends React.Component{
constructor() {
super()
this.state = {
cinemaList: [],
myText:''
}
//用axios请求数据
axios.get('https://apis.netstart.cn/maoyan/index/moreCinemas?day=2021-11-12&offset=0&limit=20&districtId=-1&lineId=-1&hallType=-1&brandId=-1&serviceId=-1&areaId=-1&stationId=-1&item&updateShowDay=true&reqId=1636710166221&cityId=1&lat=23.135636443326355&lng=1').then(res => {
console.log(res)
this.setState({
cinemaList: res.data
})
}).catch(err => {
console.log(err)
})
}
//生命周期函数更适合发送ajax请求
render() {
return (
<div>
<input value={this.state.myText} onChange={(e) => {
this.setState({
myText:e.target.value
})
}} />
{
this.getCinemaList().map(item => <dl key={item.cinemaId}>
<dt>{ item.title}</dt>
<dd>{ item.location}</dd>
</dl>)
}
</div>
)
}
getCinemaList() {
return this.state.cinemaList.filter(item => item.title.includes(this.state.myText)||item.location.includes(this.state.myText))
}
}
export default Cinema
受控组件没有操作dom,而非受控组件操作dom
import React, { Component } from "react"
class App extends Component{
a=100
state = {
list: [
{
id:1,text:'111',isChecked:false
},
{ id: 2, text: '222',isChecked:false },
{id:3,text:'333',isChecked:false}
],
myText: '',
}
render(){
return (
<div>
<input value={this.state.myText} onChange={(e) => {
this.setState({
myText:e.target.value
})
}} />
{/* //把ref绑定在input上 */}
<button onClick={this.handleClick}>add</button>
<ul>
{
this.state.list.map((item, index) => <li key={item.id}> <input type="checkbox" checked={this.state.list.isChecked} onChange={()=>this.handleCheck(index)} />
<span style={{textDecoration:item.isChecked?'line-through':''}}> {item.text}</span><button onClick={() => this.handleDel(index)}>delete</button></li>)
}
</ul>
{/* { this.state.list.length?null:<div>暂无待办事项</div>} */}
{/* { this.state.list.length===0&&<div>暂无待办事项</div>} */}
<div className="hidden">暂无待办事项</div>
</div>
)
}
handleCheck(index) {
//console.log(index)
let newList = [...this.state.list]
newList[index].isChecked = !newList[index].isChecked
this.setState({
list:newList
})
}
handleClick = (e) => {
//this.state.list.push(this.myref.current.value)不能直接修改状态
let newList = [...this.state.list]
newList.push({
id:this.state.list.length+1,
text: this.state.myText,
isChecked:false
})
this.setState({
list: newList,
myText:''
})
}
handleDel = (index) => {
console.log('del',index)
let newList =this.state.list.slice()
newList.splice(index, 1)
this.setState(
{list:newList}
)
}
}
export default App
修改的部分主要是把组件设置为受控的,并且增添的删除时加上删除线的功能,主要是通过控制组件的checked的状态来设置文字的css样式,用map方法把index传给onChange里的回调函数,并且把复选框作为list里每个元素的一个属性,
父子通信
为什么要进行父传子通信
父传子是为了更好的实现组件的复用,父亲需要提不同的要求给子组件,如果没有属性的传递,就无法实现复用
子传父是为什么?
状态是局部的,使用的时候需要传递;如果需要实现一个抽屉的效果,点击导航栏的按钮,触发一个列表,再触发关闭列表的效果;如果将导航栏和列表做成两个组件的时候,父组件App在使用这两个组件的时候,根据状态(isShow)来确定是否显示列表,这个状态肯定要写在父组件里,可是按钮被写在子组件里,按钮是否被点击的状态此时就需要传递给父组件,这就是子传父的必要性
像这样:
import React, { Component } from 'react';
class Navbar extends Component{
render() {
return (
<div style={{background:'red'}}>
<button onClick={() => {
console.log('子通知父,让父的isShow取反,这里的this.props就是父组件给子组件传递的回调函数', this.props)
this.props.event()
}}>click</button>
<span>Navbar</span>
</div>
)
}
}
class Slidebar extends Component{
render() {
return (
<div style={{ background: 'yellow' }}>
<ul>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
</ul>
</div>
)
}
}
class App extends Component {
state = {
isShow:false
}
render() {
return (
<div>
<Navbar event={() => {
console.log('父组件定义event事件')
}}></Navbar>
{this.state.isShow&&<Slidebar></Slidebar>}
</div>
);
}
}
export default App;
event这个回调函数是父传给子的,子拿到之后就可以执行
父传子通过属性(一些字符串什么的)传递,子传父通过回调函数(父如果传给子一个回调函数就证明我们需要子传父,需要子在自己的作用域里调用父传过来的回调函数)来执行(之前没理解这句话,现在真理解了)
import React, { Component } from 'react';
class Navbar extends Component{
render() {
return (
<div style={{background:'red'}}>
<button onClick={() => {
console.log('子通知父,让父的isShow取反,这里的this.props就是父组件给子组件传递的回调函数', this.props)
this.props.event()
}}>click</button>
<span>Navbar</span>
</div>
)
}
}
class Slidebar extends Component{
render() {
return (
<div style={{ background: 'yellow' }}>
<ul>
<li>qq</li>
<li>qq</li>
<li>qq</li>
<li>qq</li>
</ul>
</div>
)
}
}
class App extends Component {
state = {
isShow:false
}
handleEvent = () => {
this.setState({
isShow:!this.state.isShow
})
}
render() {
return (
<div>
<Navbar event={this.handleEvent}></Navbar>
{this.state.isShow&&<Slidebar></Slidebar>}
</div>
);
}
}
export default App;
现在就可以通过点击子组件的按钮控制父组件的列表是否显示了
非受控组件的实例
父组件触发事件更改状态以后会引起render的渲染,但是更改不了子组件里的state
import React, { Component } from 'react';
import '../../2-advance/css/01-卖座.css'
class Tabbar extends Component {
state={
list: [
{
id:1, text:'电影'
},
{
id: 2, text: '影院'
},
{
id: 3, text: '我的'
},
],
current: 0
}
render() {
// this.state.current = this.props.parentcurrent
// this.setState({
// current:this.props.parentcurrent
// })不能这么写,这么写render调用setState,setState更改完状态又调用render
return (
<div>
<ul>
{
this.state.list.map((item, index) =>
<li key={item.id} className={this.state.
current === index ? 'active' : ''} onClick={() => this.handleClick(index)}>{item.text}</li>)
}
</ul>
</div>
);
}
handleClick(index) {
console.log(index)
this.setState({
current: index
})
this.props.myEvent(index)
}
}
export default Tabbar;


状态组件、无状态组件、受控组件、非受控组件的区别
概念 | 特点 | 适用场景 |
---|---|---|
受控组件 | 值由 React 状态管理,数据流单向 | 需要严格控制的表单或输入场景 |
非受控组件 | 值由 DOM 管理,数据流双向 | 简单表单或直接操作 DOM 场景 |
状态组件 | 组件内部有状态逻辑,可以是类组件或函数组件 | 需要管理复杂状态的场景 |
无状态组件 | 组件内部没有状态逻辑,完全依赖 props |
纯展示型或逻辑简单的场景 |
受控组件的实例
核心是剥离子组件的状态,通过父组件的通信传递



const Tabbar=(props) => {
function handleClick(index){
props.myEvent(index)
}
if (!props.list) {
return <div>No data available</div>
}
return (
<div>
<ul>
{
props.list.map((item, index) =>
<li key={item.id} className={props.
current === index ? 'active' : ''} onClick={() =>handleClick(index)}>{item.text}</li>)
}
</ul>
</div>
);
}
export default Tabbar;
父组件:
import React, { Component } from "react"
import './css/01-卖座.css'
import Film from './maizuo受控/Film'
import Center from './maizuo受控/Center'
import Cinema from './maizuo受控/Cinema'
import Tabbar from './maizuo受控/Tabbar'
import Navbar from './maizuo受控/Navbar'
import { current } from "@reduxjs/toolkit"
class App extends Component{
state = {
current: 0,
list: [
{
id:1, text:'电影'
},
{
id: 2, text: '影院'
},
{
id: 3, text: '我的'
},
],
}
which() {
switch (this.state.current) {
case 0:return <Film></Film>
case 1:return <Cinema></Cinema>
case 2: return <Center></Center>
default:return null
}
}
render(){
return (
<div>
<Navbar myEvent={() => {
console.log('Navbar')
this.setState({
current:2
})
}}></Navbar>
{/* {其实感觉这里也可以拿路由来写} */}
{this.which()}
<Tabbar myEvent={(index )=>{console.log('父组件定义')
this.setState({
current:index
})
}
//通过属性传给Tabbar
} current={this.state.current} list={this.state.list}></Tabbar>
</div>
)
}
}
export default App
讨厌类组件的指来指去
父子通信的表单域组件
感觉没什么多说的,就是受控组件
import React, { Component } from 'react';
class Field extends Component {
render() {
return (
<div style={{ background: 'yellow' }}>
<label >{ this.props.label}</label>
<input type={this.props.type} onChange={(e) => { this.props.onChangeEvent(e.target.value) }} />
{/* //每次点击按钮触发回调函数并且把e.target.value传递回去,子传父通过回调函数 */}
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<h1>登录页面</h1>
<Field label='用户名' type='text' onChangeEvent={
(value)=>{console.log(value)
}}></Field>
<Field label='密码' type='password' onChangeEvent={
(value) => {console.log(value)
}}></Field>
<button >登录</button>
<button >取消</button>
</div>
);
}
}
export default App;
这是具体的设置属性为state
<input type={this.props.type} onChange={(e) => { this.props.onChangeEvent(e.target.value) }} value={this.props.value} />
{/* //每次点击按钮触发回调函数并且把e.target.value传递回去,子传父通过回调函数 */}
<Field label='用户名' type='text' onChangeEvent={
(value) => {
// console.log(value)
this.setState({
username:value
})
//读取本地存储的信息当作默认值属性传递给子组件
}} value={this.state.username}></Field>
这里把this.state.value传过去

ref版表单域组件
访问ref

import React, { Component ,} from 'react';
class Field extends Component {
state={
value:''
}
clear() {
this.setState({
value:''
})
}
render() {
return (
<div style={{ background: 'yellow' }}>
<label >{ this.props.label}</label>
<input type={this.props.type} onChange={(e) => {
this.setState({
//没有父传子子传父
value:e.target.value
})
}} />
{/* //每次点击按钮触发回调函数并且把e.target.value传递回去,子传父通过回调函数 */}
</div>
);
}
}
class App extends Component {
username=React.createRef()
password=React.createRef()
render() {
return (
<div>
<h1>登录页面</h1>
<Field label='用户名' type='text' ref={this.username}></Field>
<Field label='密码' type='password'ref={this.password}></Field>
<button onClick={() => {
console.log(this.username.current.state.value,this.password.current.state.value)
}} >登录</button>
<button onClick={() => {
this.username.current.clear()
}}>取消</button>
</div>
);
}
}
export default App;
情况的时候把value值设置回去:
<input type={this.props.type} onChange={(e) => {
this.setState({
//没有父传子子传父
value: e.target.value
})
}} value={this.state.value} />
避免暴露的使用方法,一些方法可以封装出来:
setValue(value) {
this.setValue({
value:value
})
}
<div style={{ background: 'yellow' }}>
<label >{ this.props.label}</label>
<input type={this.props.type} onChange={(e) => {
this.setValue(e.target.value)
}} value={this.state.value} />
{/* //每次点击按钮触发回调函数并且把e.target.value传递回去,子传父通过回调函数 */}
</div>