React(二)-组件和数据

1,456 阅读7分钟

一、元素和组件

const div = React.createElement( 'div',...) 这是一个React元素(d小写)

const Div = ( )=>React.createElement('div',... ) 这是一个React组件(D大写) 

JSX文件/JS文件中一个返回 React 对象的函数就是组件,(前者用标签后者用React API)

二、类组件和函数组件

定义组件最简单的方式就是编写 JavaScript 函数:

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

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

<Welcome name="xxx"/>

你同时还可以使用 ES6 的 class 来定义组件:

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

<Welcome name="xxx"/>

三、JSX做了什么

JSX在遇到我们写的标签后,JSX就是语法糖

  • <div />会被翻译为React.createElement('div') ,遇到HTML原生标签会传入字符串
  • <Welcome />翻译为React.createElement(Welcome) ,遇到不认识的标签会传入变量

React.createElement 的不同参数

  • 参数是字符串"div":创建一个div元素
  • 参数是函数:自动调用这个函数,获取其返回值
  • 参数是类:创建实例,调用实例的render方法获取返回值

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

<Welcome name="xxx">hi</Welcome>

babel成

function Welcome(props) {
  return React.createElement("h1", null, "Hello, ", props.name);
}

React.createElement(Welcome, {
  name: "xxx"
}, "hi");

React.createElement 调用 Welcome 函数,获取到React.createElement("h1", null, "Hello, ", props.name) 创建的React元素,并传入属性和子元素。

我们最好只留一个页面入口和App.vue一样,只渲染<App/>即可

四、组件的使用

和Vue一样我们只需要<xxx/>即可,传入属性的方法一样,React也不会接受<xxx></xxx>标签里的内容,正如同没有slot的 Vue 会忽视里面的内容

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

这种方式会把组件的嵌套固定住(很像没有用到slot的Vue),如果我们像创建一个Layout组件,就需要用到props.children API

五、外部数据

类组件:this.props.xxx

函数组件:函数参数.xxx

父组件传入子组件props时,<Welcome xxx="xxx"/>,<Welcome xxx={xxx}/>,和Vue的v-bind一样

六、内部数据

在 React 中,this.props 和 this.state 都代表着已经被渲染了的值,即当前屏幕上显示的值。props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。

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>
    );
  }
}

看完上面代码,你可能有几个疑问:

  • setState 是什么,为什么使用,有什么用,
  • 为什么 add 函数里不可以直接操作this.state.n

理解关键:props(“properties” 的缩写)和 state 都是普通的 JavaScript 对象

七、React的数据响应式

在 Vue 里面,直接对 data 里的 n 进行变化,Vue 会知道 n 变化了会去更新视图,Vue 劫持了 data 里的数据。

但是在 React 里并没有对 props 和 data 劫持 ,是已经渲染好的值,需要我们手动更新视图,具体怎么更新(Diff)不用深究。

新对象(setState里的对象)==>旧对象(this.state)的映射(析构)

八、给 setState 传递一个对象与传递一个函数的区别

incrementCount() {
  this.setState((state) => {
    // 重要:在更新的时候读取 `state`,而不是 `this.state`。
    return {count: state.count + 1}
  });
}

因为 setState 是异步的 —— 不要指望在调用 setState 之后,this.state 会立即映射为新的值。如果你需要基于当前的 state 来计算出新的值,那你应该传递一个函数,而不是一个对象。

incrementCount() {
  this.setState({count: this.state.count + 1});
}

handleSomething() {
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
}

  this.setState((state) => {
    return {count: state.count + 1}
  });
}

handleSomething() {
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();
}

setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

九、函数组件的state

const Grandson = () => {
  const [n, setN] = React.useState(0);
  return (
    <div className="Grandson">
      孙子 n:{n}
      <button onClick={() => setN(n + 1)}>+1</button>
    </div>
  );
};

  const [n, setN] = React.useState(0) -- 析构

const [state, setState] = useState(initialState);

第一个参数是旧数据,第二个参数新数据,右边为初始值

React.useState(0) 返回一个 state,以及更新 state 的函数。所以拿数组来析构。在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。

区别:

类组件的 setState 异步改变this.state

函数组件的 setState 不会改变 state,创建新的state

十、类组件的注意事项

  • this.state.n += 1无效 其实n已经改变了,只不过UI不会自动更新而已 调用setState才会触发UI更新(异步 更新) 因为React没有像Vue监听data一样监听state 
  • setState会异步更新UI  setState之后,state不会马 上改变,立马读state会失败 

十一、两种编程模式

Vue的编程模式:

一个对象,对应一个虚拟DOM,当对象的属性改变时,Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。,把属性相关的DOM节点全部更新 。(一个对象)

React的编程模式:

一个对象,对应一个虚拟DOM,另一个对象,对应另一个虚拟DOM 对比两个虚拟DOM,(DOM diff) 最后局部更新DOM。(state对象和setState里的新对象)。 (多个对象)

十二、复杂state

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

类组件的setState会自动合并第一层属性,第二层不会

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

函数组件的setState不会自动合并

<button onClick={() => setState({ ...state, n: state.n + 1 })}>n+1</button>
<button onClick={() => setState({ ...state, m: state.m + 1 })}>m+1</button>

使用Object.assign或者展开操作符

  changeUser() {
    const user = Object.assign({}, this.state.user);
    user.name = "jack";
    this.setState({
      // m 和 n 不会被置空
      user: user
    });
  }

十三、事件绑定-this的深入理解

<button onClick={() => this.addN()}>n+1</button>   //true
<button onClick={this.addN}>n+1</button>           //false
<button onClick={this.addN.bind(this)}>n+1</button>//true
<button onClick={this._addN}>n+1</button>          //true

  • 要理解jsx的大括号真的只是大括号,不是对象
  • 第一种由于箭头函数的特性,所以这里的this就是创建时的this
  • 第二种 this 变成 window/null , button.onclick.call(null)

<button onClick={this.handleClick}>n+1</button>的实际含义和 method = obj.method 一样,method的this是window,而obj.method里面的this是obj,this.handleClick的里面的this(不是handleClick的this)是实例,而onClick里面的this是undefined

  • 第三种 this.addN.bind(this) 两个this是同一个对象(实例)
  • 第四种 前提是 this._addN = ()=> this.addN()
  • 第五种如下

constructor(){
    super()
    this.addN = ()=> this.setState({n: this.state.n + 1})
}
render(){
    return <button onClick={this.addN}>n+1</button>
}

逻辑分析:

  1. 依然对比 method = obj.method 这个代码
  2. onClick={this.addN} 
  3. 我们需要担心的是 obj.method 里面的this,原来是obj 现在变成 window
  4. this.addN 里面的this(不是addN前面的那个this),由于放在了箭头函数里所以this不会改变依然是实例
  5. 关键在于理解里面的this的含义

constructor(){
    super()
    this.addN = ()=> this.setState({n: this.state.n + 1})
}

等价于

constructor(){
    super()   
}
addN = ()=> this.setState({n: this.state.n + 1})

类的方法可以直接放在constructor里也可以在外面,区别在于addN前面加不加this

十四、类的课外小知识

constructor(){
    super()
    this.addN = ()=> this.setState({n: this.state.n + 1})
}

等价于

constructor(){
    super()   
}
addN = ()=> this.setState({n: this.state.n + 1})

这两种addN最后会出现在实例上而

constructor(){
    super()   
}
addN(){ 
    this.setState({n: this.state.n + 1})
}
或
addN:function(){ 
    this.setState({n: this.state.n + 1})
}

出现在原型__proto__上

十五、题外话

这么点内容咋写了十几个标题晕了