一、组件状态:State和SetState
状态(state) 即数据,函数组件又叫无状态组件,只负责数据展示;类组件又叫有状态组件,负责更新UI。
1.state基本使用
(1)在类组件中直接定义state为一个对象。(或者在类组件中声明构造函数)
state = {count:0}
(2)修改数据必须用组件实例上的 setState()方法
this.setState({ count: this.state.count + 1 })
(3)修改哪些数据,就在setState配置对象里写哪些数据。
// 类组件中的状态:state数据
class StateBox extends React.PureComponent {
// 方法一:为类组件添加状态
// constructor (props) {
// super(props);
// this.state = {
// count: 0
// }
// }
// 方法二:state简写形式(推荐):这种写法是类支持的
state = {
count: 1
}
render () { return <div> </div> }
}
ReactDOM.render(<StateBox />, document.getElementById('root'))
2. 受控组件
受控组件:所有输入型的DOM,其值会随着输入被维护到state中,就是受控组件。例如:
- HTML中的表单元素是可输入的,也就是有自己的可变状态
- 而React中可变状态通常保存在state中,并且只能通过setState() 方法来修改
- React将state与表单元素值value绑定在一起,由state的值来控制表单元素的值
(1)在state中添加一个状态,作为表单元素的value值
(2)给表单元素绑定change事件,将表单元素的值设置为state的值
// 受控组件
class MyForm extends React.Component {
state = {
input: '我是普通input表单',
textarea: '我是文本框',
select: '纽约',
checkbox: false
}
handleChange = (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value
this.setState({ [e.target.name]: value })
}
render () {
return (
<div>
<input name="input" value={this.state.input} onChange={this.handleChange} />
<textarea name="textarea" value={this.state.textarea} onChange={this.handleChange}></textarea>
<select name="select" value={this.state.select} onChange={this.handleChange}>
<option>商丘</option>
<option>纽约</option>
<option>洛杉矶</option>
</select>
<input
name="checkbox"
type="checkbox"
checked={this.state.checkbox}
onChange={this.handleChange}
/>
</div>
)
}
}
3.非受控组件
(1)调用 React.createRef() 方法创建ref对象
(2)将创建好的 ref 对象添加到文本框中
(3)通过ref对象获取到文本框的值
class App extends React.Component {
constructor(){
super()
//创建 ref
this.txtRef = React.createRef()
}
// 获取文本框的值
getTxt =() => {
console.log(this.txtRef.current.value)
}
render(){
return (
<div>
<input type ="text" ref={this.txtRef} />
<button onClick ={this.getTxt}>获取值</button>
</div>
)
}
}
4. 页面中用到的静态数据
可以写在 类组件之前。
使用这个数据时 不加this,直接写变量名。
二、setState详细说明
修改state数据;更新UI视图。
1.setState()异步更新
(1)setState()更新数据是 异步 的。
(2)可以 多次 调用setState(),但是后面的setState 不能依赖前面setState的值。
(3)多次调用setState,只会触发 一次render()。
2.setState()推荐语法
this.setState((state,props)=>{
return { }
})
(1)通过参数state和props可以获取到 最新的 state数据和props数据。
(2)解决了 多次调用setState() 时,后面的setState不能依赖前面的setState数据的问题。
3.setState()第二个参数
this.setState((state,props)=>{ },()=>{ })
(1)第二个参数是一个 回调函数。
(2)这个回调函数会在 数据更新后,也就是页面重新渲染后 立即执行。
// setState()推荐语法
this.setState(
(state, props) => {
return {
count: state.count + 1
}
},
() => {
console.log('数据更新后,也就是页面重新渲染后,会立即执行这个回调函数');
}
)
三、props基本使用
- 组件是封闭的,要 接收外部数据 应该通过props来实现
- props的作用:接收传递给组件的数据
- 传递数据:给组件标签添加属性
1.函数组件
函数组件通过 参数props 接收外部传递的数据。
const Dog = (props) => {
// 注意:props是一个对象,不要直接放到jsx中渲染
console.log(props);
return (
<div>{props.name}</div>
)
}
ReactDOM.render(<Dog name="狗" />, document.getElementById('root'))
2.类组件
(1)类组件通过 this.props 获取外部传递的数据。
class Cat extends React.Component {
// 如果类组件中有构造函数,props需要作为参数传递给super()函数
constructor (props) {
super(props)
console.log(props);
}
render () {
return (
<ul>
<li>{this.props.age}</li>
<li>{this.props.friends[1]}</li>
<li>{this.props.fn()}</li>
<li>{this.props.tag}</li>
</ul>
)
}
}
// 可以传递各种类型数据:数组、函数、jsx
ReactDOM.render(
<Cat
name="狗"
age={6}
friends={['Tom', 'Jerry']}
fn={() => console.log('这是一个函数')}
tag={<p>这是一个jsx</p>}
/>,
document.getElementById('root')
)
(2)传递数据时的写法
① React中通过 展开运算符展开对象,一次传递多个数据。
② 下面的 { } 代表的是里面要写js语法;... 代表的是展开运算符,要展开后面的对象。
③ 原生js 中,展开运算符 不能展开对象;只有 React中 ,可以用于 标签上数据的传递。
④ {...obj} 的形式代表 复制 一个新的对象,相当于深拷贝。
class Index extends React.Component {
state = {
name: '',
age:''
}
render () {
// 通过展开运算符,直接将一个对象传递给子组件
return <Son {...this.state}></Son>
}
}
3.传递的数据类型
(1)可以是字符串、数字、数组、函数、jsx等。示例如上。
(2)props是 只读属性,不能对值进行修改.
(3)使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到this.props,其他的地方是可以拿到的。
四、组件通信
1.父向子传值
(1)父组件调用子组件时,给子组件绑定 自定义属性。
(2)自定义属性值为父组件的 私有数据。
(3)子组件内通过 props 进行接收。(两种组件接收props的方法见上面)
// 父向子传值
class Father extends React.Component {
state = {
msg: '我是父组件的数据'
}
render () {
return (
//父组件绑定自定义属性
<Son msg={this.state.msg} />
)
}
}
class Son extends React.Component {
render () {
return (
//子组件通过props接收
<div>{this.props.msg}</div>
)
}
}
2.子向父传值
(1)父组件调用子组件时,绑定一个 自定义事件。
(2)子组件内触发自定义事件,通过参数传递数据。
(3)自定义事件要提前在父组件内声明,通过 参数 可以得到子组件传递的数据。
// 子向父传值
class Father extends React.Component {
// 父组件内声明自定事件
showMsg = data => {
console.log('父组件拿到了子组件数据:', data);
}
render () {
return (
// 父组件调用子组件时绑定自定义事件
<Son getMsg={this.showMsg} />
)
}
}
class Son extends React.Component {
state = {
msg: '我是子组件的数据'
}
handleClick = () => {
// 子组件触发自定义事件并传递数据
this.props.getMsg(this.state.msg)
}
render () {
return (
<button onClick={this.handleClick} >点我</button>
)
}
}
3.兄弟组件传值
(1)在两兄弟的最近的公共父组件内存放 共享状态。
(2)公共父组件职责:提供共享状态 ;提供操作共享状态的方法
(3)要通讯的子组件只需要通过props接收状态或操作状态的方法
// 兄弟组件传值
class Father extends React.Component {
// 父组件提供共享数据
state = {
number: 0
}
// 父组件声明自定义事件
handleClick = (data) => {
this.setState({ number: this.state.number + data })
}
render () {
return (
<div>
{/* 父组件向子组件传递数据 */}
<Child1 number={this.state.number} />
{/* 父组件通过自定义事件获取子组件传递的值 */}
<Child2 onAdd={this.handleClick} />
</div>
)
}
}
class Child1 extends React.Component {
render () {
return (
// 子组件接收父组件的数据
<h1>{this.props.number}</h1>
)
}
}
class Child2 extends React.Component {
handleClick = () => {
this.props.onAdd(5)
}
render () {
return (
// 子组件触发自定义事件,修改父组件的值
<button onClick={this.handleClick}>点我</button>
)
}
}
ReactDOM.render(<Father />, document.getElementById('root'))
4.Context基本使用
如果嵌套层级比较深,父组件可以用 Context 将数据传递给后代子组件。
(1)调用 React.createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
const { Provider, Consumer } = React.createContext()
(2)顶级父组件 中,用<Provider></Provider>包裹子组件,并通过 value 属性传递数据。
(3)子组件中,在<Consumer></Consumer>中,有一个回调函数,其参数为接收的数据。
// Context基本使用
// 调用 React.createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
const { Provider, Consumer } = React.createContext()
class ZuXian extends React.Component {
render () {
return (
// 使用Provider组件,并通过value属性向子代传递数据
<Provider value="我是你祖宗">
<SunZi></SunZi>
</Provider>
)
}
}
class SunZi extends React.Component {
render () {
return (
// 子组件以这种形式获取数据
<div>
<Consumer>
{
data => <span>{data}</span>
}
</Consumer>
</div>
)
}
}
ReactDOM.render(<ZuXian></ZuXian>, document.getElementById('root'))
5.props的children属性
- children属性: 表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
- children属性与普通的props一样,值可以是任意值(文本、react元素、组件、甚至是函数)
// props的children属性
const Children = (props) => {
return (
<div>{props.children}</div>
)
}
ReactDOM.render(<Children>我是组件的子节点</Children>, document.getElementById('root'))
6.props校验
- 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
- 如果传入的数据不对,可能会导致报错
- props校验:允许在创建组件的时候,指定props的类型、格式等
- 作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
(1)安装包 prop-types
yarn add prop-types | npm i prop-types
(2)页面导入prop-types 包
import 自定义名称 from 'prop-types'
(3)使用 组件名.propTypes={} 来给组件的props添加校验规则。(propTypes是固定的,由React提供)
(4)校验规则通过 PropTypes对象 来指定。(PropTypes也是固定的,由依赖包提供)
// props校验
class JiaoYan extends React.Component {
render () {
return (
<div>{this.props.color}</div>
)
}
}
JiaoYan.propTypes = {
color: PropTypes.String,
age: PropTypes.number.isRequired, //数字类型且必填
job: PropTypes.shape({
time: PropTypes.String,
money: PropTypes.number
})
}
ReactDOM.render(
<JiaoYan
color='pink'
age={18}
></JiaoYan>,
document.getElementById('root')
)
常见的约束规则
创建的类型:
array、bool、func、number、object、string
React元素类型:
element
必填项:
isRequired
特定结构的对象:
shape({})
-
更多的约束规则
7.props默认值
通过 组件名.defaultProps对象 指定props的默认值。如果传递了该值,则默认值被覆盖。
JiaoYan.defaultProps = {
pagesize: 10
}
ReactDOM.render(
<JiaoYan
color='pink'
age={18}
></JiaoYan>,
document.getElementById('root')
)
8. peops校验与默认值写到类中
class Index extends React.Component {
render () {
return <div></div>
}
// 用 static修饰后,propsTypes对象和defaultProps对象被挂载到类上
static propTypes = {}
static defaultProps = {}
五、类组件中的 ref
1.字符串形式的ref(老版本)
(1)用法类似于vue2.0,在组件上添加 ref 属性。
(2)在方法中用 this.refs.名称(没有$) 获取组件实例。
class Index extends React.Component {
showInput = () => {
console.log(this.refs.inputRef);
}
render () {
return (
<div>
<input type="text" onBlur={this.showInput} ref="inputRef"/>
</div>
)
}
}
2.回调函数形式的ref (中间版)
(1)回调函数:自己定义的函数,别人来调用。
(2)在组件上添加 ref 属性,属性值是一个 回调函数。
(3)通过回调函数的 参数 拿到组件实例,直接 挂载到this上。
class Index extends React.Component {
showInput = () => {
const { btnRef } = this
console.log(btnRef);
}
render () {
return (
<div>
<button onClick={this.showInput} ref={(currentNode)=>this.btnRef=currentNode}>点击</button>
</div>
)
}
}
(4)关于回调 refs 的说明
如果 ref 回调函数是以 内联函数(就是上面的方式) 的方式定义的,在 更新过程中 它会被执行 两次,第一次传入参数 null,紧接着第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 先清空旧的 ref 再设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是 无关紧要的。
class Index extends React.Component {
showInput = () => {
const {btnRef2} = this
console.log(btnRef2);
}
// 挂载ref到this
setRef = (currentNode) => {
this.btnRef2 = currentNode
}
render () {
return (
<div>
<button onClick={this.showInput} ref={this.setRef}>点我</button>
</div>
)
}
}
3.createRef()
(1)用 createRef()函数创建一个 ref容器,挂载到 类的实例对象上。
(2)在组件上添加ref属性,属性值就是 ref容器,React会把组件实例放到ref容器中。
class Index extends React.Component {
// 创建ref容器
btnRef3 = React.createRef()
showInput = () => {
console.log(this.btnRef3.current);
}
render () {
return (
<div>
<button onClick={this.showInput} ref={this.btnRef3}>点击一下</button>
</div>
)
}
}
六、事件处理函数
1.类组件中,事件处理函数的写法
- render()函数中的this指向 组件实例;(constructor构造函数也是)
- 事件处理函数中的this是 undefined,无法调用组件实例的setState()方法
方案一:利用箭头函数(了解)
在箭头函数中返回事件处理函数的调用,将render()函数的this传递给事件处理函数
// 方案一:利用箭头函数
class ChangeThis1 extends React.PureComponent {
state = {
count: 1
}
handleClick () {
this.setState({ count: this.state.count + 2 })
}
render () {
return (
<div>
<div>{this.state.count}</div>
<button
//在箭头函数中返回事件处理函数的调用,将render()函数的this传递给事件处理函数
onClick={() => this.handleClick()}
>
+1
</button>
</div>
)
}
}
ReactDOM.render(<ChangeThis1 />, document.getElementById('root'))
方案二:通过bind方法,更改事件处理函数中的this(了解)
在构造函数中将事件处理函数中的this绑定为组件实例
// 方案二:通过bind方法,更改事件处理函数中的this(了解)
class ChangeThis2 extends React.PureComponent {
constructor () {
super()
this.state = {
count: 1
}
// 在构造函数中将事件处理函数中的this绑定为组件实例
// 1.下面代码是右侧内容赋值给左侧,此时this代表的 【ChangeThis2】 实例对象上并没有handleClick函数,
// 2.而是通过原型链拿到了原型对象上的 【handleClick】函数,并同过bind方法改变了this指向。
// 3.bind方法返回一个新的函数,此时在this实例对象上我们手动创建了【newHandler】函数进行接收。
// 4.最终事件绑定的处理函数,是实例对象上的【newHandler】。
this.newHandleClick = this.handleClick.bind(this)
}
handleClick () {
this.setState({ count: this.state.count + 3 })
}
render () {
return (
<div>
<div>{this.state.count}</div>
<button
onClick={this.newHandleClick}
>
+1
</button>
</div>
)
}
}
ReactDOM.render(<ChangeThis2 />, document.getElementById('root'))
方案三:利用箭头函数形式的class实例方法
类组件绑定事件、修改state数据,参照这个即可
// 方案三:利用箭头函数形式的class实例方法
class ChangeThis3 extends React.PureComponent {
state = {
count: 1
}
// 直接将事件处理函数改为箭头函数即可
handleClick = () => {
this.setState({ count: this.state.count + 10 })
}
render () {
return (
<div>
<div>{this.state.count}</div>
<button
onClick={this.handleClick}
>
+1
</button>
</div>
)
}
}
ReactDOM.render(<ChangeThis3 />, document.getElementById('root'))
注意:
(1)这是给事件添加处理方法需要用箭头函数,如果是获取接口数据的方法,可以直接写在配置对象中。
(2)onClick={this.handleClick} 是将 handleClick函数本身 作为点击事件的处理函数。
(3)onClick={this.handleClick()} 是将handleClick函数的 返回值 作为点击事件的处理函数。
(4)onClick={(record)=>{this.handleClick(record)}} 是将一个箭头函数作为点击事件的处理函数,箭头函数的参数可以拿到事件对象或其他内部实参。
class Index extends React.Component {
handleClick1 = (event) => {
console.log('输入框的value是:',event.target.value); // false
}
handleClick2 = (a) => {
return (event) => {
console.log(a); // '自定义内容'
console.log('输入框的value是:',event.target.value); // false
}
}
handleClick3 = (event,a) => {
console.log('输入框的value是:', event.target.value); // false
console.log(a); // false
}
render () {
return (
<div>
<button value="false" onClick={this.handleClick1}>按钮1</button>
<button value="false" onClick={this.handleClick2('自定义参数')}>按钮2</button>
<button value="false" onClick={ (event)=>this.handleClick3(event,'自定义参数')}>按钮3</button>
</div>
)
}
}