【React全解1】React组件、props、state&setState

1,543 阅读6分钟

目录

  • 组件简介
  • 类组件
  • 函数组件
  • props(外部数据)和state(内部数据)&setState
  • 复杂state

一、组件简介

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

组件就是用于组合的物件。在编程中,组件的作用就是使代码具有模块化的意义,能让代码有更好的可读性和维护性。

在React中,定义组件有两种方式,函数组件和类组件,不成文的规定:组件首字母大写,元素首字母小写

二、类组件

创建类组件的方式有两种,一种是ES5(过时)的写法,一种是最新的ES6写法。

1、ES5方式(过时)

由于ES5不支持class,才会有这种方式。

import React from 'react';
const A=React.createClass({
	render(){
		return (
			<div>Hello,world!</div>
		)
	}
})
export default A;

2、ES6方式

现在,我们一般都使用ES6的方式创建类组件。

import React from 'react';
class B extends React.Component{
	/* constructor是初始化的过程 */
	constructor(props){
		super(props);
	}
	render(){
		return (
			<div>Hello,world!</div>
		)
	}
}
export default B;

三、函数组件

函数组件好是好,对比类组件,我们面临两个问题。函数组件没有state和生命周期函数

没有State:ReactV16.8.0退出的Hooks API中的useState可以代替。

没有生命周期:ReactV16.8.0退出的Hooks API中的useEffect可以代替。

函数组件的一些模拟类组件的API

1、创建方式

// # 使用箭头函数创建函数组件
const Hello1=(props)=>{
	return (
		<div>{props.msg}</div>
	)
}
// # 简写的箭头函数创建组件
const Hello2=props=><div>{props.msg}</div>
// # 函数function创建
function Hello(props) {
	return (
		<div>{props.msg}</div>
	)
}

2、使用函数组件

<函数名 />或函数名()形式

ReactDOM.render(<Hello1>,document.queryselector('root'));
# 或者
ReactDOM.render(Hello1(),document.queryselector('root'))

四、props(外部数据)和state(内部数据)&setState

1、props

props是外部数据,需要在某个组件被用到的时候外部对他进行传递数据

类组件直接读取属性:this.props.xxx

函数组件直接读取参数:props.xxx

  • props的作用
  1. 接受外部数据,只能读不能写!!!外部数据由父组件传递
  2. 接受外部函数,在恰当的时机调用该函数,该函数一般是父组件的函数

  • props在类组件中的初始化

初始化操作是在constructor中完成的。

import React from 'react';
class B extends React.Component{
	constructor(props){
		super(props)
	}
	render(){}
}

注意

  1. 要么不初始化,即不写constructor
  2. 如何要初始化,必须写全套constructor,不写super()会直接报错
  3. 初始化后,this.props就是外部数据对象的地址
  • props在函数组件中的初始化
function App(props) {
	return (
		<div>{props}</div>
	)
}

2、读取props数据

  • 主类组件A给副类父组件B传递props数据
class App extends React.Component {
	render() {
		return (
			<div>
		  	  这里是父组件
		  	  <B name='高圆圆' age='18'/>
			</div>
		)
	}
}
  • 副类组件B中读取props数据
import React from 'react';
class B extends React.Component{
	constructor(props){
		super(props)
	}
	render(){
		return (
			<>
			// 类组件中通过this.props.xxx读取数据
			  <div>
				name:{this.props.name}
				<div>
				  age:{this.props.age}
				</div>
			  </div>
			</>
		)
	}
}

  • 函数主组件给函数父组件传递props数据
function App(props) {
	return (
		<div>
		  这里是函数主组件
		  <Hello name='袁姗姗' age='18'/>
		</div>
	)
}
  • 函数组件读取props数据
import React from 'react';
const Hello=(props)=>{
	return (
		<>
		// 函数组件通过参数props读取数据
		  <div>
			name:{props.name}
			  <div>
				age:{props.age}
			  </div>
		  </div>
		</>
	)
}

小结

  1. props为外部数据,只允许使用的组件读取props外部数据,不允许写。因为这不符合React的规范,要修改数据,也只能由该props数据的主人来修改。
  2. 若要修改props数据,可通过函数调用,在该props数据的主人的组件中对props进行修改。

3、state(内部数据)&setState

state是内部数据,表示在组件内使用的数据。

数据不可变原理

对比Vue:我们可以直接this.state.n+=1;this.setState(this.state),但是React的初衷则不是让我们这样去操作数据,这是不符合React的设计理念的。React不会像Vue一样直接对data进行代理监听。我们需要的不是直接对原始数据进行操作而是需要产生一个新的对象或值

setState则是一个API专门用来修改state中的值的,它是一个异步操作。在setState()括号中最好写一个箭头函数去表示要对数据进行操作的代码。如:setState(i=>i+1)

  • 类组件中使用state&setState()

需要在初始化constructor的时候添加state。

import React from 'react';
class B extends React.Component{
	constructor(props){
		super(props);
		this.state={
			n:0,
			user:{name:'frank',age:18}
		}
	}
	render(){
		<>
		  <div>
			 n:{this.state.n}
			 <hr/> 
			//  点击后触发n+1,但是触发this.state.n+=1页面则不会更新
			 <button onClick={this.setState((state)=>{return {n:this.state.n+1}})}>
			+1
			</button>
		  </div>
		  <div>
		 	user:{this.state.user} 
		  </div>
		</>
	}
}
  • 函数组件使用state&setState()

需要用到React的一个API:React.useState(initialState)

以下操作n的setN同样是个异步操作,产生一个新的对象或值

React.useState详解传送门

import React from 'react';
const App=()=>{
	const [n,setN]=React.useState(0);
	// n就是初始值0,setN为操作初始值0的API
	return (
		<div>
		  n:{n}
		  <button onClick={()=>setN(i=>i+1)}>+1</button>
		</div>
	)
}

小结

  1. this.state.n+=1无效?其实n值已经改变了,但是React不会去触发UI更新。只有调用setState才会触发更新,因为React没有像Vue一样对state进行代理监听
  2. setState是异步操作,即异步更新渲染页面,推荐使用setState(函数)的方式。函数表示对n的操作,如:setState(i=>i+1)
  3. this.setState(this.state)不推荐?这是因为在React的理念中遵循不可变数据的思想,它不希望我们修改旧的state。常用代码:setState({n:this.state.n+1})
  4. 在函数组件中,通过React.useState(initialState)来模拟使用state,也要通过setX()来更新UI,也是一个异步操作

五、复杂state

什么叫复杂的state?就是当我们的state中数据不只有n时,如同时存在n和m或一个对象user时。当我们只对一个数据进行修改时,会发生什么呢?你会发现另外的数据如m会被置空

这是因为setState只会shallow merge。它只合并第一层的数据

1、类组件中的复杂state

  • 类组件中有n和m

以下情况中,只set n或者m的值,另外一个值不会被置空。

import React from 'react';
class A extends React.Component {
  constructor() {
    super();
    this.state = {
      n: 0,
      m: 0
    };
  }
  addN() {
    this.setState({ n: this.state.n + 1 });
    // m 会被覆盖为 undefined 吗?
  }
  addM() {
    this.setState({ m: this.state.m + 1 });
    // n 会被覆盖为 undefined 吗?
  }
  render() {
    return (
      <div>
         n: {this.state.n}
        <button onClick={() => this.addN()}>n+1</button>
        m: {this.state.m}
        <button onClick={() => this.addM()}>m+1</button>
      </div>
    );
  }
}
  • 类组件中有对象user{}

在以下拥有user对象时,你会发现,第一层属性n和m不会被置空,但我只修改user里面的name时,age却被置空了。

解决方案:使用...操作符将原先数据拷贝过来或者Object.assign,再进行修改

import React from 'react';
class B extends React.Component {
  constructor() {
    super();
    this.state = {
      n: 0,
      m: 0,
      user: {
        name: "frank",
        age: 18
      }
    };
  }
  addN() {
    this.setState({ n: this.state.n + 1 });
    // m 会被覆盖为 undefined 吗?
  }
  addM() {
    this.setState({ m: this.state.m + 1 });
    // n 会被覆盖为 undefined 吗?
  }
  changeUser() {
    this.setState({
      // m 和 n 不会被置空
      user: {
		//   要想age不被置空=> ...this.state.user;
        name: "jack"
        // age 被置空
      }
    });
  }
  render() {
    return (
      <div>
        n: {this.state.n}
        <button onClick={() => this.addN()}>n+1</button>
        m: {this.state.m}
        <button onClick={() => this.addM()}>m+1</button>
        <hr />
        <div>user.name: {this.state.user.name}</div>
        <div>user.age: {this.state.user.age}</div>
        <button onClick={() => this.changeUser()}>change user</button>
      </div>
    );
  }
}

2、函数组件中的复杂state

在函数组件中React不会自动合并第一层属性。

  • 函数组件里面有n和m
import React from 'react';
const C = () => {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  return (
    <div>
    	n:{n}
      <button onClick={() => setN(i=>i+1)}>n+1</button>
      m:{m}
      <button onClick={() => setM(i=>i+1)}>m+1</button>
    </div>
  );
};
  • 函数组件中对象形式

只改变对象中一个数据的值,另外的数据会被置空。

解决方案:使用...操作符拷贝原先的数据或Object.assign

import React from 'react';
const D = () => {
  const [state, setState] = React.useState({n:0,m:0});
  return (
    <div>
		n:{n}
		/* 注意,这个按钮会将m的值置空 */
      <button onClick={() => setState({n:state.n+1})}>n+1</button>
      m:{m}
      <button onClick={() => setM({...state,n:state.n+1})}>m+1</button>
    </div>
  );
};

小结

  1. 类组件中的setState会自动合并第一层属性,但是并不会合并第二层及后面的属性。
  2. 函数组件中的setX不会自动合并属性值。
  3. 为了解决上面的问题,属性或值被置空。我们一般使用...操作符或者Object.assign中转。