React 组件

芝麻凛 2021年10月14日 121次浏览

React 组件

Element V.S. Component

元素与组件

  • const div = React.createElement('div',...),这是一个React元素(d小写)
  • const Div = () => React.createElement(‘div’,...) 这是一个React组件(D大写)

组件是什么

  • 能与其它物件组合起来的 , 就是组件
  • 就目前而已,一个返回React 元素的函数就是组件,在Vue里,一个构造选项就可以表示一个组件

React两种组件

一、函数组件

function Welcome(props){
	return <h1>Hello, {props.name}</h1>
}
// 使用方法: <Welcome name="frank" />

二、类组件

class Welcome extends React.Component{
	render(){
		return <h1> Hello,{this.props.name}</h1>
	}
}
// 使用方法 : <Welcome name="frank" />

会被翻译成什么

  • 会被翻译成 React.createElement('div')
  • 翻译为 React.createElement(Welcome)

  • 可以用 babel online 直接翻译React看看效果

React.createElement 的逻辑

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

示例1

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.setState({ n: this.state.n + 1 });
  }
  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);

添加props(外部数据)

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 messageForGrandson="孙贼你好" />
      </div>
    );
  }
}

const Grandson = props => {
  return (
    <div className="Grandson">
      我是孙子,我爸对我说「{props.messageForGrandson}」
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// 类组件直接读取属性 this.props.xxx,函数组件直接读取参数 props.xxx

添加state(内部数据)

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((state) => { //推荐方式
      const n = state.n + 1;
      console.log(n);
      return { 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);
// 类组件用this.state读,this.setState写,函数组件用useState返回数组,第一项读、第二项写。

类组件注意事项

this.state.n +=1 无效?

  • 其实n已经改变了。只不过UI不会自动更新,调用setState才会触发UI更新(异步更新)
  • 因为React没有像 Vue 监听data一样监听 state

setState 会异步更新UI

  • setState之后,state不会马上改变,立马读state会失败
  • 更推荐的方式是setState(函数)

this.setState(this.state) 不推荐

  • React希望我们不要修改旧state(不可变数据)
  • 常用代码:setState({n:state.n+1})
  • React这些是一种函数式理念

函数组件

  • 跟类组件类似的地方:也要通过setX(新值)来更新UI
  • 跟类组件不同的地方:没有this,一律用参数和变量

两种变成模型

image20211014203358007.png

Vue的编程模型

一个对象,对应一个虚拟DOM,当对象的属性改变时,把属性相关的DOM节点全部更新

注:Vue为了其他考量,也引入了虚拟DOM和DOM Diff,上图只是简化

React的编程模型

一个对象,对应一个虚拟DOM。另一个对象,对应另一个虚拟DOM。

对比两个虚拟DOM,找出不同(DOM diff)最后局部更新DOM

复杂 state

如果state里面不止有n怎么办

// 类组件里面有n 和 m
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
    };
  }
  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>
        <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);

// 函数组件 里面有n和 m
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
    };
  }
  addN() {
    this.setState({ n: this.state.n + 1 });
  }
  addM() {
    this.setState({ m: this.state.m + 1 });
  }
  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>
        <Grandson />
      </div>
    );
  }
}

const Grandson = () => {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  return (
    <div className="Grandson">
      孙子 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);

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
    };
  }
  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>
        <Grandson />
      </div>
    );
  }
}

const Grandson = () => {
  const [state, setState] = React.useState({
    n: 0,
    m: 0
  });
  return (
    <div className="Grandson">
      孙子 n:{state.n}
      <button onClick={() => setState({ ...state, n: state.n + 1 })}>  //不加...state m会被变成undefined
        n+1
      </button>
      m:{state.m}
      <button onClick={() => setState({ ...state, m: state.m + 1 })}>
        m+1
      </button>
    </div>
  );
};

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

总结

  • 类组件的setState会自动合并第一层属性,但是并不会合并第二层属性,解决方法:使用Object.assign或者...操作符
  • 函数组件的setX则完全不会帮忙合并,需要合并是手动写...操作符把之前的复制过来合并。

事件绑定

类组件的事件绑定

<button onClick={()=> this.addN()}>n+1</button>
// 传一个函数给 onClick 即可
// 还有一种写法:
<button onClick={this.addN.bind(this)}>n+1</button>
// 这样写是可以的,因为它返回一个绑定了当前this的新函数,但是这样写太麻烦,还不如第一种
//可以用 this._addN = () => this.addN() ,给箭头函数取个名字,然后写成
<button onClick={this._addN}>n+1</button>

//错误写法:这样写会使this.addN里的this变成window
<button onClick={this.addN}>n+1</button>

// 终极写法
class Son extends React.Component{
    addN = () => this.setState({n: this.state.n + 1})
	constructor(){
        this.addN = () => this.setState({n:this.state.n+1})
    }
	// 上面两种写法完全等价
	//下面两种写法完全等价
	addN(){this.setState({n.this.state.n+1})
    addN:function(){
        this.setState({n:this.state.n+1})
    }
}

这四种addN有什么区别?

  • 上面两种在对象上,下面两种在原型上,下面的addN的this可变成window,上面的addN不会

  • 上面两个函数是对象本身的属性,这意味着每个Son组件都有自己的addN,如果有两个Son,就有两个addN

  • 下面两个函数是对象的共用属性(也就是原型上的属性),这意味着所有Son组件共用一个addN

    为什么this会变/不会变?

  • 所有函数的this都是参数,由调用决定所以可变

  • 唯独箭头函数的this不变,因为箭头函数不接受this

React V.S. Vue

相同点

  • 都是对视图的封装,React是用类和函数表示一个组件,而Vue是通过构造选项构造一个组件
  • 都提供了createElement的XML简写,React提供的是JSX语法,而Vue提供的是模板写法

不同点

  • React是把HTML 放在JS里写(HTML in JS),而Vue是把JS放在HTML里写(JS in HTML)

总结

关于React的知识点

  • 两种方式引入React&ReactDOM
  • React.createElement(‘div’|函数|类)
  • 类组件、函数组件如何获取外部数据props
  • 类组件、函数组件如何使用内部数据state
  • 类组件如何绑定事件,this很麻烦,使用上面两种写法搞定
  • 函数组件绑定事件非常简单,不用this,React的特点:能不帮你做,就不帮你做