目录

React-属性与状态江湖从验证到表单受控的实战探险

《React 属性与状态江湖:从验证到表单受控的实战探险》

属性初识

属性能解决两个大问题:通信和复用

props.js:
import React, { Component } from 'react'
import Navbar from './Navbar'

export default class App extends Component {
    state = {
        a:100
    }
    render() {
    return (
      <div>
        <div>
          <h2>首页</h2>
          <Navbar title="首页" leftshow={false}/>
        </div>
        <div>
          <h2>列表</h2>
          <Navbar title="列表" leftshow={true}/>
        </div>
        <div>
          <h2>购物车</h2>
          <Navbar title="购物车" leftshow={true}/>
        </div>
      </div>
    )
  }
}
index.js:
import React, { Component } from 'react'

export default class Navbar extends Component {
  state = {
    //只能内部自己用的,外面无法改变
  }
  // 属性是父组件传来的,this.props
  render() {
    let { title,leftshow } = this.props
    console.log(leftshow)
    return (<div>
        {leftshow && <button>返回</button>}
        Navbar-{title}
        <button>home</button>
        </div>)
  }
}

属性验证

添加属性验证:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Navbar extends Component {
  state = {
    //只能内部自己用的,外面无法改变
  }
  // 属性是父组件传来的,this.props
  render() {
    let { title,leftshow } = this.props
    console.log(leftshow)
    return (<div>
        {leftshow && <button>返回</button>}
        Navbar-{title}
        <button>home</button>
        </div>)
  }
}

//类属性
Navbar.propTypes = {
    title:PropTypes.string,
    leftshow:PropTypes.bool
}

如果是在外面证明是类属性,在里面是对象属性

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Navbar extends Component {
  state = {
    //只能内部自己用的,外面无法改变
  }
  static propTypes = {
    title:PropTypes.string,
    leftshow:PropTypes.bool
}
  // 属性是父组件传来的,this.props
  render() {
    let { title,leftshow } = this.props
    console.log(leftshow)
    return (<div>
        {leftshow && <button>返回</button>}
        Navbar-{title}
        <button>home</button>
        </div>)
  }
}

这也是一种写法

默认属性

怎么加上默认属性呢?

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Navbar extends Component {
  state = {
    //只能内部自己用的,外面无法改变
  }
  static propTypes = {
    title:PropTypes.string,
    leftshow:PropTypes.bool
}
  // 属性是父组件传来的,this.props
  render() {
    let { title,leftshow } = this.props
    console.log(leftshow)
    return (<div>
        {leftshow && <button>返回</button>}
        Navbar-{title}
        <button>home</button>
        </div>)
  }
}

// 对属性加上默认值
Navbar.defaultProps = {
    leftshow:true
}

也可以像上面一样进行改写

属性注意

import React, { Component } from 'react'
import Navbar from './Navbar'

export default class App extends Component {
    state = {
        a:100
    }
    render() {
      //从上面的父组件传来的一个对象
      var obj = {
          title:"测试",
          leftshow: false
      }
    return (
      <div>
        <div>
          <h2>首页</h2>
          <Navbar title="首页" leftshow={false}/>
        </div>
        <div>
          <h2>列表</h2>
          <Navbar title="列表" leftshow={true}/>
        </div>
        <div>
          <h2>购物车</h2>
          <Navbar title="购物车" leftshow={true}/>
        </div>
        <Navbar title={obj.title} leftshow={obj.leftshow}/>
        <Navbar {...obj}/>
      </div>
    )
  }
}

当接收的和传过来的一致的时候,就可以简写了

可以这样来控制组件的显示:

props.js:

import React, { Component } from 'react'
import Navbar from './Navbar'
import Sidebar from './Sidebar'

export default class App extends Component {
  render() {
    return (
      <div>
        <Navbar title="导航">

        </Navbar>
        <Sidebar bg="yellow" position="left">
        
        </Sidebar>
      </div>
    )
  }
}

index.js:

import React from 'react'

export default function Sidebar(props) {
  let {bg,position} = props
  var obj1 = {
    left:0
  }
  var obj2 = {
    right:0
  }
  var obj = {
    background:bg,
    width:"200px",
    position:"fixed"
  }
  var styleobj = position==="left"?{...obj,...obj1}:{...obj,...obj2}
  return (
    <div style={styleobj}>
        <ul>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
            <li>11111</li>
        </ul>
    </div>
  )
}

状态VS属性

相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)

不同点:

  1. 属性能从父组件获取,状态不能

  2. 属性可以由父组件修改,状态不能

  3. 属性能在内部设置默认值,状态也可以,设置方式不一样

  4. 属性不在组件内部修改,状态要在组件内部修改

  5. 属性能设置子组件初始值,状态不可以

  6. 属性可以修改子组件的值,状态不可以 state 的主要作用是用于组件保存、控制、修改自己的可变状态。

state 在组件内部初始化,可以被 组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制 的数据源。 state 中状态可以通过 this.setState 方法进行更新, setState 会导致组件的重新渲染。

props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参 数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props ,否则组件的 props 永远保持 不变。

没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件 (stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有 状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

孩子无法直接修改属性

import React, { Component } from 'react'

class Child extends Component{
    render(){
        return <div>
            child-{this.props.text}
            <button>click-child</button>
        </div>
    }
}

export default class App extends Component {
    state = {
        text:"111111111"
    }
    render() {
    return (
      <div>
        <button onClick={()=>{
            this.setState({
                text:'222222222'
            })
        }}>click</button>
        <Child text={this.state.text}/>
      </div>
    )
  }
}

表单的受控与非受控

受控组件非受控组件在狭义上是看是否调用ref

但是在广义上是React组件的数据渲染是否被调用者传递的props完全控制,控制则是受控组件,否则不是受控组件

非受控

如果React要编写一个非受控组件,那么久可以使用ref来从DOM节点中获取表单数据,就是非受控组件,比如这样:

import React, { Component } from 'react'

export default class App extends Component {
    myusername = React.createRef()
    render() {
    return (
      <div>
        <h1>登录页</h1>
        <input type='text' ref={this.myusername}
        value="kerwin"/>
        <button onClick={()=>{
            console.log(this.myusername.current )
        }}>登录</button>
        <button onClick={()=>{
          this.myusername.current.value = ""
        }}>重置</button>
      </div>
    )
  }
}

这种输入框是输不进去东西的,但是改成defaultvalue就不一样了

import React, { Component } from 'react'

export default class App extends Component {
    myusername = React.createRef()
    render() {
    return (
      <div>
        <h1>登录页</h1>
        <input type='text' ref={this.myusername}
        defaultValue="kerwin"/>
        <button onClick={()=>{
            console.log(this.myusername.current )
        }}>登录</button>
        <button onClick={()=>{
          this.myusername.current.value = ""
        }}>重置</button>
      </div>
    )
  }
}

这就是它在非受控组件中的应用

在原生JS中,onInput是监听输入的变化,onChange是输入变化且焦点移开才监听一次

但是它在React中和onInput一样了

受控

https://i-blog.csdnimg.cn/direct/46dba465822b419ea1789e666199ff0a.png

看看受控组件:

import React, { Component } from 'react';

// 假设这里定义了 Child 组件
class Child extends Component {
    render() {
        return (
            <div>
                接收到的值: {this.props.myvalue}
            </div>
        );
    }
}

export default class App extends Component {
    state = {
        username: "kerwin"
    };

    render() {
        return (
            <div>
                <h1>登录页</h1>
                <input
                    type='text'
                    value={this.state.username}
                    onChange={(evt) => {
                        console.log("onChange", evt.target.value);
                        this.setState({
                            username: evt.target.value
                        });
                    }}
                />
                <button
                    onClick={() => {
                        console.log(this.state.username);
                    }}
                >
                    登录
                </button>
                <button
                    onClick={() => {
                        this.setState({
                            username: ''
                        });
                    }}
                >
                    重置
                </button>
                {/* 直接传递 state 中的 username */}
                <Child myvalue={this.state.username} />
            </div>
        );
    }
}

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value ,这使得 React 的 state 成为 唯一数据源。

由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而 更新。

对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他 事件处理函数重置,但这意味着你需要编写更多的代码

受控影院查询案例

对之前的影院案例做更改

import React, { Component } from 'react';
import axios from 'axios';
import BetterScroll from 'better-scroll';

export default class Cinema extends Component {
    constructor(props) {
        super(props);
        this.state = {
            cinemaList: [],
            mytext:""
        };
        this.wrapperRef = React.createRef();
        this.bs = null;
    }

    componentDidMount() {
        axios({
            url: 'https://m.maizuo.com/gateway?cityId=610100&ticketFlag=1&k=1315991',
            headers: {
                'x-client-info': '{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161","bc":"610100"}',
                'x-host': 'mall.film-ticket.cinema.list'
            }
        })
          .then((res) => {
                console.log(res.data);
                this.setState({
                    cinemaList: res.data.data.cinemas,
                    backcinemaList: res.data.data.cinemas
                }, () => {
                    if (this.wrapperRef.current) {
                        this.bs = new BetterScroll(this.wrapperRef.current);
                    }
                });
            })
          .catch((error) => {
                console.error('请求出错:', error);
            });
    }

    render() {
        const { cinemaList } = this.state;
        return (
            <div>
                <input value={this.state.mytext}onChange={(evt)=>{
                    this.setState({
                        mytext:evt.target.value
                    })
                }}/>
                <div
                    ref={this.wrapperRef}
                    className="wrapper"
                    style={{ height: '500px', background: 'yellow', overflow: 'hidden', position: 'relative' }}
                >
                    <div className='content' style={{ padding: '10px' }}>
                        {this.getCinemaList().map((item) => (
                            <dl key={item.cinemaId}>
                                <dt>{item.name}</dt>
                                <dd>{item.address}</dd>
                            </dl>
                        ))}
                    </div>
                </div>
            </div>
        );
    }

    getCinemaList(){
        return this.state.cinemaList.filter(item =>
            item.name.toUpperCase().includes(this.state.mytext.toUpperCase()) ||
            item.address.toUpperCase().includes(this.state.mytext.toUpperCase())
        );
    }
}

获取数据之后进行betterScroll初始化,在修改完状态后,setState后传回调函数,再重新初始化betterScroll解决数据更新后betterScroll长度过长的问题

案例受控todolist

更改成受控组件的写法:

import React, { Component } from 'react'

export default class App extends Component {
  state = {
    mytext:'',
    list: [
      {
        id: 1,
        mytext: 'aaa',
      },
      {
        id: 2,
        mytext: 'bbb',
      },
      {
        id: 3,
        mytext: 'ccc',
      },
    ],
  }
  render() {
    return (
      <div>
        <input value={this.state.mytext} onChange={(evt)=>{
            this.setState({
                mytext:evt.target.value
            })
        }}/>
        <button
          onClick={() => {
            this.handleClick()
          }}
        >
          add2
        </button>
        <ul>
          {this.state.list.map((item,index) => (
            <li key={item.id}>
                <span dangerouslySetInnerHTML={
                    {
                    __html:item.mytext
                }}>

                </span>
                <button onClick={()=>{
                    this.handleDelClick(index)
                }}>
                    删除
                </button>
                </li>
          ))}
        </ul>
        <div className={this.state.list.length === 0 ?'':'hidden'}>暂无待办事项</div>
      </div>
    )
  }
  handleClick = () => {
    let newlist = [...this.state.list]
    newlist.push({
        id:Math.random()*100000,        //生成不同id的函数
        mytext:this.state.mytext
  })
    this.setState({
      list: newlist,
      mytext:''
    })
  }

  handleDelClick(index){
        console.log(index)
        //不要直接修改状态,可能会造成不可预期的问题
        let newlist = this.state.list.concat()

        newlist.splice(index,1)
        this.setState({
            list:newlist
        })
  }
}

我们现在还想要添加一个效果:删除的时候不直接删除 ,而是添加删除线

添加状态,再添加删除线效果:

import React, { Component } from 'react'

export default class App extends Component {
  state = {
    mytext:'',
    list: [
      {
        id: 1,
        mytext: 'aaa',
        isChecked:false
      },
      {
        id: 2,
        mytext: 'bbb',
        isChecked:false
      },
      {
        id: 3,
        mytext: 'ccc',
        isChecked:true
      },
    ],
  }
  render() {
    return (
      <div>
        <input value={this.state.mytext} onChange={(evt)=>{
            this.setState({
                mytext:evt.target.value
            })
        }}/>
        <button
          onClick={() => {
            this.handleClick()
          }}
        >
          add2
        </button>
        <ul>
          {this.state.list.map((item,index) => (
            <li key={item.id}>
                <input type='checkbox' checked={item.isChecked} onChange={()=>this.handleChecked(index)}/>
                {item.isChecked?'删除':'不删除'}
                <span dangerouslySetInnerHTML={
                    {
                    __html:item.mytext
                }}style={{textDecoration:item.isChecked?"line-through":""}}>

                </span>
                <button>完成</button>
                <button onClick={()=>{
                    this.handleDelClick(index)
                }}>
                    删除
                </button>
                </li>
          ))}
        </ul>
        <div className={this.state.list.length === 0 ?'':'hidden'}>暂无待办事项</div>
      </div>
    )
  }
  handleChecked = (index)=>{
    let newlist = [...this.state.list]
    newlist[index].isChecked = !newlist[index].isChecked
    this.setState({
        list:newlist
    })    
  }
  handleClick = () => {
    let newlist = [...this.state.list]
    newlist.push({
        id:Math.random()*100000,        //生成不同id的函数
        mytext:this.state.mytext,
        isChecked:false
  })
    this.setState({
      list: newlist,
      mytext:''
    })
  }

  handleDelClick(index){
        console.log(index)
        //不要直接修改状态,可能会造成不可预期的问题
        let newlist = this.state.list.concat()

        newlist.splice(index,1)
        this.setState({
            list:newlist
        })
  }
}