React的数据、事件
组件的数据挂载方式
React中数据分为两个部分
- 属性
- 状态(可以频繁变化)
- Vue中数据只有状态这一种类型
属性(props)
props是正常从外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是可以通过父组件主动重新渲染的方式来传入新的props
- 内部设置的属性是不去更改的
属性是描述性质、特点的,组件自己不能随意更改
之前的组件代码里面有props
的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
:
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.name}是一个构建UI的库</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title name="React" />
<Content name="React.js" />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
设置组件的默认props
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
// 使用类创建的组件,直接在这里写static方法,创建defaultProps
static defaultProps = {
name: 'React'
}
render () {
return (
<h1>欢迎进入{this.props.name}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.name}是一个构建UI的库</p>
)
}
// 使用箭头函数创建的组件,需要在这个组件上直接写defaultProps属性
Content.defaultProps = {
name: 'React.js'
}
class App extends Component {
render () {
return (
<Fragment>
{/* 由于设置了defaultProps, 不传props也能正常运行,如果传递了就会覆盖defaultProps的值 */}
<Title />
<Content />
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
props.children
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
class Title extends Component {
render () {
return (
<h1>欢迎进入{this.props.children}的世界</h1>
)
}
}
const Content = (props) => {
return (
<p>{props.children}</p>
)
}
class App extends Component {
render () {
return (
<Fragment>
<Title>React</Title>
<Content><i>React.js</i>是一个构建UI的库</Content>
</Fragment>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
使用prop-types检查props
React其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的props
设定参数检查,需要安装和使用prop-types:
$npm i prop-types -S
状态
**状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,**使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
组件自己的状态只能自己更改
定义状态
import React, { Component,Fragment } from 'react';
class StateComp extends Component{
//第一种方式,直接定义
state = {
msg: '中秋节快乐'
}
render () {
const { msg } = this.state
return (
<Fragment>
<p> 直接在类中定义: { msg } </p>
</Fragment>
)
}
}
//--------------------------------------------------------------------------------
//第二种方式(推荐)
class StateComp extends Component{
constructor ( props ) { // 官方推荐我们使用这一种
console.log( props ) // 父组件绑定在子组件身上的属性
super( props ) // this.props = props
this.state = {
msg: '中秋节快乐'
}
render () {
const { msg } = this.state
return (
<Fragment>
<p> 直接在类中定义: { msg } </p>
</Fragment>
)
}
}
export default StateComp
this.props
和
this.state是纯js对象,在vue中,data属性是利用
Object.defineProperty处理过的,更改data的数据的时候会触发数据的
getter和
setter,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法
setState`。
setState
import React, { Component,Fragment } from 'react';
class StateComp extends Component{
constructor ( props ) {
console.log( props )
super( props )
this.state = {
msg: '中秋节快乐'
}
changeMsg = () => {
this.setState({
msg: '国庆节快乐'
})
/*
this.setState(() => {
return {
msg: '国庆节快乐'
}
}) 回调函数方式
*/
}
render () {
const { msg } = this.state
return (
<Fragment>
<p> 直接在类中定义: { msg } </p>
<button onClick = { this.changeMsg }>改变</button>
</Fragment>
)
}
}
export default StateComp
-
setState它是异步的,但是可以实现同步,它是React封装方法
-
setState有两个参数
-
第一个参数可以是对象,也可以是方法 【return一个对象】,我们把这个参数叫做
updater
-
第二个参数是一个回调函数,表示异步,这个函数中我们可以用来执行一些副作用【 其他业务 】
- 获取最新的state
-
一个思考: setState底层是如何实现的? 换言之: 它是如何实现视图更新的?(日后解决)
属性与状态的异同点
-
相同点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
-
不同点:
- 属性能从父组件获取,状态不能
- 属性可以由父组件修改,状态不能
- 属性能在内部设置默认值,状态也可以
- 属性不在组件内部修改,状态要改 【 属性只能外部修改,内部不允许修改】
- 属性能设置子组件初始值,状态不可以
- 属性可以修改子组件的值,状态不可以
-
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过this.setState
方法进行更新,setState
会导致组件的重新渲染 -
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的
props
,否则组件的props
永远保持不变。 -
无状态组件与有状态组件
-
没有
state
的组件叫无状态组件(stateless component) -
设置了 state 的叫做有状态组件(stateful component)
-
因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
-
无状态组件也就是函数式组件
有状态组件就是类组件
-
-
-
react性能优化一个方案: 就是多使用无状态组件( 函数式组件 )
状态提升
- 如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
受控组件与非受控组件
- React组件的数据渲染是否被调用 是通过 传递过来的
props
完全控制,控制则为受控组件,否则非受控组件。
渲染数据
-
条件渲染
{ condition ? '❤️取消' : '?收藏' } //三目运算符 { condition && '取消'||'收藏' } //短路逻辑
-
列表渲染
import React, { Component } from 'react'; class List extends Component{ render() { return ( <div> { this.props.lists && this.props.lists.movieList.map( item => { //item指的就是数组中每一个对象 return ( <div key = { item.id }> //记得要加key <img src = { item.img.replace('w.h','128.180') }/> <h3> { item.nm } </h3> <p> { item.star } </p> </div> ) }) } </div> ) } } export default List
React的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是React并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是
key
,这个key
必须是每个元素唯一的标识 -
dangerouslySetHTML
对于富文本创建的内容,后台拿到的数据是这样的:
content = "<p>React.js是一个构建UI的库</p>"
处于安全的原因,React当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用
dangerouslySetHTML
属性,它允许我们动态设置innerHTML
import React from 'react'; import './App.css'; class App extends React.Component{ constructor(props) { super(props) this.state = { xmlData: '<h3> xml类型数据 </h3>' } } render () { return ( <div className="App"> <p dangerouslySetInnerHTML={{__html: this.state.xmlData}} ></p> // 注意这里是两个下下划线 __html </div> ); } } export default App;
事件处理
绑定事件
- 采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写
onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。
事件handler的写法
-
直接在render里写行内的箭头函数(不推荐)
<Fragment> <button onClick = { () => { alert(1) }}> CompA 中的事件一 </button> </Fragment>
-
在组件内使用箭头函数定义一个方法(推荐)
class EventCompB extends Component{ alertFun = () => { console.log('compb', this ) alert( 2 ) } render () { return ( <Fragment> <button onClick = { this.alertFun }> CompB 事件第二种 </button> EventCompB </Fragment> ) } }
-
直接在组件内定义一个非箭头函数的方法,然后在render里直接使用
onClick={this.handleClick.bind(this)}
(不推荐) -
直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(推荐)
class EventCompD extends Component{ constructor(props) { super(props) this.alertFun = this.alertFun.bind( this ) } alertFun () { console.log( this ) alert( 4 ) } render () { return ( <Fragment> <button onClick = { this.alertFun }> CompD 事件第四种 </button> EventCompD </Fragment> ) } }
-
注意: 事件不能定义在函数式组件中
Event对象
- React中的
event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法 - 事件对象中的值很多都是null,但是可以正常使用
事件传参
- 在
render
里调用方法的地方外面包一层箭头函数
import React from 'react';
import './App.css';
/*
事件参数
形式参数: 方法定义时定义的,相当于在方法内部定义了一个变量
实际参数: 方法调用时传入的实际值
*/
class App extends React.Component{
argFun = ( a,b ) => {
alert( a + b )
}
render () {
return (
<div className="App">
<h3> react - 事件-传参 </h3>
<button onClick = {() => { this.argFun( 1,2 ) }}> 事件传参第一种 </button>
</div>
);
}
}
export default App;
- 在
render
里通过this.handleEvent.bind(this, 参数)
这样的方式来传递
render () {
return (
<div className="App">
<h3> react - 事件-传参 </h3>
<button onClick = { this.alertFun.bind( this,1,2 ) }> 事件传参第二种 </button>
</div>
);
}
- 通过
event
传递 - 做一个子组件, 在父组件中定义方法,通过
props
传递到子组件中,然后在子组件件通过this.props.method
来调用
用户输入
-
通过e事件对象
- e.target.value
-
通过ref绑定
-
普通绑定
<input type="text" ref = "username"/>
const newVal = this.refs.username.value
-
函数形式(推荐)
<input type="text" ref = { el => this.username = el }/>
const newVal = this.username.value
不要过量使用ref , 会导致性能的浪费
-
-
表单中,原先就有内容需要用defaultValue属性,复选框用defaultChecked
<input type = "text" defaultValue = "请输入"/>
<input type = "checkbox" defaultChecked/>