类组件和函数组件

221 阅读4分钟

函数组件

 function Welcome(props){
   return <h1>Hello,{props.name}</h1>
 }
 <Welcome name="jack"/>

类组件

class Welcome extends React.Component {
  render() {
    return <h1>Hello,{this.props.name}</h1>
  }
}
<Welcome name="jack"/>

<Welcome/>会被翻译成什么?

  • <div/>会被翻译为React.createElement('div)
  • <Welcome/>会被翻译为React.createElement(Welcome)

React.createElement的逻辑

  • 如果传入一个字符串'div',则会创建一个div
  • 如果传入一个函数,则会调用该函数,获取其返回值
  • 如果传入一个类,则在类前面加个new(这会导致执行constructor),获取一个组件对象,然后调用对象的render,方法,获取其返回值

类组件 VS 函数组件

我们通过+1的练习来感受函数组件和类组件数据获取的区别(内部数据)

//style.css  

.App {
  font-family: sans-serif;
  text-align: center;
  background: grey;
  padding: 10px;
}

.Son {
  background: yellow;
  padding: 10px;
}

.Grandson {
  background: green;
  padding: 10px;
  color: white;
}
//index.js

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
  return (
    <div className="App">
      爸爸
      <Son />
    </div>
  );
}
class Son extends React.Component {
  constructor() {
    super();
    this.state = {
      n: 0
    };
  }
  add() {
    // this.state.n += 1;
    this.setState({ n: this.state.n + 1 });
    // this.setState(()=>{this.state.n+1})
    console.log(this.state.n);
  }
  render() {
    return (
      <div className="Son">
        儿子 n: {this.state.n}
        <button onClick={() => this.add()}>+1</button>
        <Grandson />
      </div>
    );
  }
}
const Grandson = () => {
  const [n, setN] = React.useState(0);
  return (
    <div className="Grandson">
      孙子n:{n}
      <button onClick={() => setN(n + 1)}>+1</button>
    </div>
  );
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

渲染结果如图:

react.gif 通过该示例可以看出:

  • 类组件用this.state读,this.setState写
  • 函数组件用useState返回数组,第一项读,第二项写
    再来看它们是怎么获取外部数据的(props)
    同样地,我们通过示例来观察:
//index.js  

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
  return (
    <div className="App">
      我是爸爸
      <Son messageForSon="儿子你好" />
    </div>
  );
}
class Son extends React.Component {
  render() {
    return (
      <div className="Son">
        我是儿子,我爸对我说:{this.props.messageForSon}
        <Grandson messageForSon="孙子你好" />
      </div>
    );
  }
}
const Grandson = (props) => {
  return (
    <div className="Grandson">我是孙子,我爸对我说:{props.messageForSon}</div>
  );
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

渲染结果如下:

image.png 通过该示例可以看出:

  • 类组件直接读取属性this.props.xxx
  • 函数组件直接读取参数props.xxx

复杂的state

问:如果组件里不止有n,还有m,那怎么办?
先说结论:
如果类组件里有n和m,会自动合并第一层,但是不会自动合并第二层,要使用Object.assign或者...操作符把先前的数据先拷贝过来,再修改。
如果函数组件里有m和n,函数组件的setX不会自动合并

还是通过练习来验证吧:

类数组件的m和n

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App  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 className="Son">
        儿子 n: {this.state.n}
        <button onClick={() => this.addN()}>n+1</button>
        m: {this.state.m}
        <button onClick={() => this.addM()}>m+1</button>
      </div>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

渲染结果如下:

react2.gif

可以证实: 类组件的第一层会被自动合并

 import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      爸爸
      <Son />
    </div>
  );
}

class Son 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: {
        name: "jack"
        // age 被置空
      }
    });
  }
  render() {
    return (
      <div className="Son">
        儿子 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>
    );
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

渲染结果如下:

react3.gif
通过上图可以看出: 第二层属性中的use.age被置空了,解决方法:要使用Object.assign或者...操作符

// 沿用以上代码,changeUser这个函数改为:
//Object.assign
 changeUser() {
    const user = Object.assign({}, this.state.user);
    user.name = "jack";
    this.setState({
      // m 和 n 不会被置空
      user: user
    });
  }
  
 // ...操作符 
  changeUser() {
    this.setState({
      // m 和 n 不会被置空
      user: {
        ...this.state.user, // 复制之前的所有属性
        name: "jack"
        // age 被置空
      }
    });
  }

react4.gif
这时user.age就不会被被置空了。

函数组件的m和n

以下方式不会置空m

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      爸爸
      <Grandson />
    </div>
  );
}

const Grandson = () => {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  return (
    <div className="Son">
      儿子 n:{n}
      <button onClick={() => setN(n + 1)}>n+1</button>
      m:{m}
      <button onClick={() => setM(m + 1)}>m+1</button>
    </div>
  );
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

react5.gif
以下这种方式m会被置空

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      爸爸
      <Granson />
    </div>
  );
}

const Granson = () => {
  const [state, setState] = React.useState({
    n: 0,
    m: 0
  });
  return (
    <div className="Son">
      儿子 n:{state.n}
      <button onClick={() => setState({ n: state.n + 1 })}>n+1</button>
      m:{state.m}
      <button onClick={() => setState({ m: state.m + 1 })}>m+1</button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

react6.gif
可以发现,m被置空了,解决方法为: 用...操作符

// 沿用以上代码  
const Granson = () => {
  const [state, setState] = React.useState({
    n: 0,
    m: 0
  });
  return (
    <div className="Son">
      儿子 n:{state.n}
      <button onClick={() => setState({...state, n: state.n + 1 })}>n+1</button>
      m:{state.m}
      <button onClick={() => setState({ ...state,m: state.m + 1 })}>m+1</button>
    </div>
  );
};

React VS Vue

相同点

  • 都是相对视图的封装,React是用类和函数表示一个组件,而Vue是通过构造选项构造一个组件
  • 都提供了createElemt的XML简写,React提供的是JSX语法,Vue提供的是模板写法(语法超多)
    不同点
    React是把HTML放在JS里写(HTML in JS),而Vue是把JS放在HTML里写(JS in HTML)

总结

  • React有两种组件:函数组件和类组件
    获取外部数据(props)
  • 类组件直接读取属性this.props.xxx
  • 函数组件直接读取参数props.xxx
    获取内部数据(state)
  • 类组件this.state读,this.setState
  • 函数组件useState返回数组,第一项读,第二项写
    复杂的state
  • 类组件的setState会自动合并第一层属性,但是不会合并第二层属性,可以使用Object.assign...操作符
  • 函数组件的setX完全不会帮你合并,合并的话就用...操作符