十、表单中的受控组件与非受控组件
1、非受控组件
React要编写一个非受控组件,可以使用ref来从DOM节点中获取表单数据,就是非受控组件。
例如:
import React, {
Component } from 'react'
export default class App extends Component {
mytext = React.createRef()
render() {
return (
<div>
<input type="text" ref={
this.mytext} defaultValue="asdad" />
<button onClick={
() => {
console.log(this.mytext.current.value); }
}>获取Value</button>
<button onClick={
() => {
this.mytext.current.value = ""
}}>重置</button>
</div >
)
}
}
因为非受控组件将真实数据储存在DOM节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
在 React 渲染生命周期时,表单元素上的value将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望React能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个defaultValue属性,而不是 value。(defaultValue是在非受控下使用,而value是在受控的时候使用)
同样,checkbox radio支持defaultChecked,select和textarea支持defaultValue
1、受控组件
import React, { Component } from 'react'
export default class App extends Component {
state = {
mytext: "123"
}
render() {
return (
<div>
<input type="text" value={this.state.mytext} onChange={(e) => {
this.setState({
mytext: e.target.value
})
}} />
<button onClick={() => {
console.log(this.state.mytext);
}}>获取Value</button>
<button onClick={() => {
this.setState({
mytext: ""
})
}}>重置</button>
</div>
)
}
}
原理和vue的双向数据绑定类似(v-model)
由于在表单元素上设置了value属性,因此显示的值将始终为this.state.value ,这使得React的state成为唯一数据源。由于handlechange在每次按键时都会执行并更新React的state,因此显示的值将随着用户输入而更新。
对于受控组件来说,输入的值始终由React的state驱动。你也可以将value 传递给其他UI元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。
注意: 另一种说法(广义范围的说法),React组件的数据渲染是否被调用者传递的props完全控制,控制则为受控组件,否则非受控组件。
十一、组件间通信
父子组件内通信
- 传递数据(父传子)与传递方法(子传父)
父组件:
import React, {
Component } from 'react'
import './css/01-maizuo.css'
import Home from "./compontents/home"
import Cinema from "./compontents/cinema"
import My from "./compontents/My"
import Tabbar from './compontents/Tabbar'
import Navbar from './compontents/Navbar'
export default class APP extends Component {
state = {
list: [
{
id: 0,
text: "首页"
},
{
id: 1,
text: "电影"
},
{
id: 2,
text: "我的"
}
],
current: 0
}
switch() {
switch (this.state.current) {
case 0:
return <Home></Home>
case 1:
return <Cinema></Cinema>
case 2:
return <My></My>
default:
return null
}
}
render() {
return (
<div>
<Navbar event={
this.tarrget} />
{
//表达式--支持函数表达式
this.switch()
}
<Tabbar current={
this.state.current} list={
this.state.list} event={
this.tarrget}></Tabbar>
</div>
)
}
tarrget = (index) => {
this.setState({
current: index
})
}
}
子组件Tabbar:
import React, {
Component } from 'react'
export default class Tabbar extends Component {
render() {
return (
<div>
<div>
<ul>
{
this.props.list.map((item, index) =>
<li key={
item.id} className={
this.props.current === index ? 'active' : ''} onClick={
() => this.click(index)}>{
item.text}</li>)
}
</ul>
</div>
</div>
)
}
click = (index) => {
this.props.event(index)
}
}
子组件Navbar:
import React, {
Component } from 'react'
export default class Navbar extends Component {
render() {
return (
<div>
<div style={
{
background: "yellow", textAlign: "center", overflow: "hidden" }}>
<button style={
{
float: "left" }}>back</button>
<span>卖座电影</span>
<button style={
{
float: "right" }} onClick={
() => this.goHome(0)}>home</button>
</div>
</div>
)
}
goHome = (index) => {
this.props.event(index)
}
}
这里是父组件向子组件传递属性,子组件通过回调函数返回返回值,采用的是受控的方式对子组件进行控制,意思是在子组件中不过多的使用state状态,而是通过父组件的属性进行控制,减少组件内的状态。
2.ref标记 (父组件拿到子组件的引用,从而调用子组件的方法)
代码:
import React, {
Component } from 'react'
export default class App extends Component {
username = React.createRef()
password = React.createRef()
render() {
return (
<div>
<Field label="用户名" type="text" ref={
this.username}></Field>
<Field label="密码" type="password" ref={
this.password}></Field>
<button onClick={
this.login}>登录</button>
<button onClick={
this.set}>设置登录名</button>
<button onClick={
() => {
this.password.current.clear()
this.username.current.clear()
}}>取消</button>
</div>
)
}
login = () => {
console.log(this.username.current.state.value);
console.log(this.password.current.state.value);
}
set = () => {
this.username.current.setValue(123)
}
}
class Field extends Component {
state = {
value: ""
}
clear = () => {
this.setState({
value: ""
})
}
setValue = (value) => {
this.setState({
value: value
})
}
render() {
return (
<div>
<label>{
this.props.label}</label>
<input onChange={
(e) => {
this.setValue(e.target.value)
}
} value={
this.state.value} type={
this.props.type} />
</div>
)
}
}
这里我们通过对子组件绑定ref 获取组件中的状态和方法,在父组件中进行对状态和方法的改变。类似于面向对象的思想。
非父子组件内通信
1.状态提升(中间人模式)
React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件 上.在父组件上改变这个状态然后通过props分发给子组件。总之就是,我们在处理两个兄弟组件时,可以通过回调的方式兄弟1组件把信息传递到父组件内,再通过props父组件传递到兄弟2组件,就可以收到信息了。
- 发布订阅模式实现
首先我们先看这部分代码(发布订阅的核心代码):
//调度中心
var bus = {
list:[],
//订阅
subscribe(callback){
// console.log(callback)
this.list.push(callback)
},
//发布
publish(text){
//遍历所有的list, 将回调函数执行
// console.log(this.list)
this.list.forEach(callback=>{
callback && callback(text)
})
}
}
解读代码:我们在一个组件中订阅一个消息通过回调函数的方式传递,把每次订阅的回调放在一个全局变量数组中,在发布中,我们去遍历这个数组,将里面的回调函数进行执行。
案例:
js:
import React, {
Component } from 'react'
import axios from 'axios'
//调度中心
var bus = {
list:[],
//订阅
subscribe(callback){
// console.log(callback)
this.list.push(callback)
},
//发布
publish(text){
//遍历所有的list, 将回调函数执行
// console.log(this.list)
this.list.forEach(callback=>{
callback && callback(text)
})
}
}
export default class App extends Component {
constructor(){
super()
this.state = {
filmList:[],
}
axios.get(`/test.json`).then(res=>{
console.log(res.data.data.films)
this.setState({
filmList:res.data.data.films
})
})
}
render() {
return (
<div>
{
/* {this.state.info} */}
{
this.state.filmList.map(item=>
<FilmItem key={
item.filmId} {
...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
)
}
}
/*受控组件*/
class FilmItem extends Component{
render(){
// console.log(this.props)
let {
name, poster,grade,synopsis} = this.props
return <div className="filmitem" onClick={
()=>{
// console.log(synopsis)
bus.publish(synopsis)
}}>
<img src={
poster} alt={
name}/>
<h4>{
name}</h4>
<div>观众评分:{
grade}</div>
</div>
}
}
class FilmDetail extends Component{
constructor(){
super()
this.state = {
info:""
}
bus.subscribe((info)=>{
console.log("我再filmDetail中定义",info)
this.setState({
info:info
})
})
}
render(){
return <div className="filmdetail">
{
this.state.info}
</div>
}
}
- context状态树传参
import React, {
Component } from 'react'
import axios from 'axios'
const GlobalContext = React.createContext() //创建context对象
export default class App extends Component {
constructor(){
super()
this.state = {
filmList:[],
info:""
}
axios.get(`/test.json`).then(res=>{
console.log(res.data.data.films)
this.setState({
filmList:res.data.data.films
})
})
}
render() {
return (
<GlobalContext.Provider value={
{
call:"打电话",
sms:"短信",
info:this.state.info,
changeInfo:(value)=>{
this.setState( {
info:value
})
}
}}>
<div>
{
/* {this.state.info} */}
{
this.state.filmList.map(item=>
<FilmItem key={
item.filmId} {
...item} ></FilmItem>
)
}
<FilmDetail ></FilmDetail>
</div>
</GlobalContext.Provider>
)
}
}
/*受控组件*/
class FilmItem extends Component{
render(){
// console.log(this.props)
let {
name, poster,grade,synopsis} = this.props
return (
<GlobalContext.Consumer>
{
(value)=>{
console.log(value)
return <div className="filmitem" onClick={
()=>{
console.log(synopsis)
// this.props.onEvent(synopsis)
// value.info = "2222222"
// console.log(value)
value.changeInfo(synopsis)
}}>
<img src={
poster} alt={
name}/>
<h4>{
name}</h4>
<div>观众评分:{
grade}</div>
</div>
}
}
</GlobalContext.Consumer>
)
}
}
class FilmDetail extends Component{
render(){
return (
<GlobalContext.Consumer>
{
(value)=><div className="filmdetail">
detail-{
value.info}
</div>
}
</GlobalContext.Consumer>
)
}
}
注意:GlobalContext.Consumer内必须是回调函数,通过context方法改变根组件状态
context优缺点: 优点:跨组件访问数据 缺点:react组件树种某个上级组件shouldComponetUpdate 返回false,当context更新时,不 会引起下级组件更新
十二、插槽
import React, {
Component } from 'react'
class Child extends Component{
render(){
return <div>
child
{
/* 插槽 vue slot,具名插槽 */}
{
this.props.children[2]}
{
this.props.children[1]}
{
this.props.children[0]}
</div>
}
}
class Swiper extends Component{
render(){
return <div>
{
this.props.children}
</div>
}
}
export default class App extends Component {
render() {
return (
<div>
<Swiper>
<div>111111</div>
<div>222222</div>
<div>333333</div>
</Swiper>
<Child>
<div>11111111</div>
<div>22222222</div>
<div>33333333</div>
</Child>
</div>
)
}
}
这里通过this.props.children来获取写在组件里面的东西
如果里面的东西想要获取多个div,也可以写成数据this.props.children[0],this.props.children[1]这样来得到,如果不写成数组this.props.children,则是获得到全部的div。
作用:
- 为了复用
- 一定程度减少父子通信。