React:类组件(下)

发布于:2025-03-16 ⋅ 阅读:(36) ⋅ 点赞:(0)

表单中的非受控组件

设置默认值,使用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

使用受控组件修改之前写的todolist:
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里每个元素的一个属性,

注意 : 另一种说法(广义范围的说法), React 组件的数据渲染是否被调用者传递的 props 完全控制,控制则
为受控组件,否则非受控组件。

父子通信

为什么要进行父传子通信

父传子是为了更好的实现组件的复用,父亲需要提不同的要求给子组件,如果没有属性的传递,就无法实现复用

子传父是为什么?

状态是局部的,使用的时候需要传递;如果需要实现一个抽屉的效果,点击导航栏的按钮,触发一个列表,再触发关闭列表的效果;如果将导航栏和列表做成两个组件的时候,父组件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;
也就是说render察觉到状态被其父组件改变了会渲染,但是只会在子组件第一次执行的时候创建列表在列表里声明current的值,而之后无论current怎么改变,只会触发渲染,而不会真正的改变列表里的current这个状态
这算是状态组件比较不方便的地方就是提升代码维护的难度

状态组件、无状态组件、受控组件、非受控组件的区别

概念 特点 适用场景
受控组件 值由 React 状态管理,数据流单向 需要严格控制的表单或输入场景
非受控组件 值由 DOM 管理,数据流双向 简单表单或直接操作 DOM 场景
状态组件 组件内部有状态逻辑,可以是类组件或函数组件 需要管理复杂状态的场景
无状态组件 组件内部没有状态逻辑,完全依赖 props 纯展示型或逻辑简单的场景

受控组件的实例

核心是剥离子组件的状态,通过父组件的通信传递

只由自己控制的,就定义在state里,由外界控制的,通过props传入,记住这条规则就行了
接收props
为了减少状态的使用,并且让父组件通过更改props的属性值来控制子组件,应该用props而不是state
这是类组件的做法,如果是函数式组件就没有这么麻烦,因为属性可以直接当参数传递:
子组件:
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

通过ref绑定的Field,获取Field组件的函数clear:
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>
低耦合好维护,直来直去,不像受控组件绕来绕去