屬性初識
屬性能解決兩個大問題:通信和復用
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.propsrender() {let { title,leftshow } = this.propsconsole.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.propsrender() {let { title,leftshow } = this.propsconsole.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.propsrender() {let { title,leftshow } = this.propsconsole.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.propsrender() {let { title,leftshow } = this.propsconsole.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} = propsvar 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一樣了
受控
看看受控組件:
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><inputtype='text'value={this.state.username}onChange={(evt) => {console.log("onChange", evt.target.value);this.setState({username: evt.target.value});}}/><buttononClick={() => {console.log(this.state.username);}}>登錄</button><buttononClick={() => {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})}}/><divref={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})}}/><buttononClick={() => {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})}}/><buttononClick={() => {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].isCheckedthis.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})}
}