React 核心概念(2)

React 核心概念(2)

 

1.组件介绍及 props

 

1.1 组件分类

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

 
组件分为 class 组件函数组件 两种,下面给出两种组件的简单示例:

// 函数组件
function FunctionComponent(name, address) {
  return `${name}${address}读书!`;
}

// class 组件
class ClassComponent extends React.Component {
  render() {
    return <h1>张三在北京读书!</h1>;
  }
}

 

1.2 组件渲染
function FunctionComponent(props) {
  const { name, address } = props;
  return `${name}${address}读书!`;
}

const element = <FunctionComponent name="张三" address="北京" />

ReactDOM.render(
  element,
  document.getElementById('root') // <div id="root" />
);

 
下面来简单分析一下执行过程:

 
首先,element 是 JSX 语法,根据上一节内容知道,最后 element 应该是这样的:

const element = {
  type: 'div',
  props: {
    id: 'root',
    children: {
      type: 'h1',
      props: {
        name: '张三',
        address: "北京",
        children: '张三在北京读书1',
      },
    }
  }
};

 
由上面的element的第6行可以看到,虽然我们是定义了一个组件,实际上经过 BabelReact.createElement() 的转换之后,拿到的是用于描述页面展示内容的 React 元素

 
其次,将此 element 传入 ReactDOM.render() 中作为第一个参数,并传入需要被挂载的 DOM 节点作为第二个参数,即可将该 element 元素渲染成一个 DOM 节点。

 
注: 组件名称必须以大写字母开头。

 

1.3 组合组件

 
组件可以在其输出中引用其他组件。

// Parent,Child 都是组件
function Parent() {
  return (
    <div>
      <Child />
    </div>
  );
}

 

1.4 提取组件

 
将组件拆分为更小的组件。

 
什么时候需要考虑拆分组件?

 
如果 UI 中有一部分被多次使用(Button,Panel,Avatar),或者组件本身就足够复杂(App,FeedStory,Comment),那么它就是一个可复用组件的候选项。

 

1.5 Props 的只读性

 
无论是使用函数组件还是 class 组件,都决不能修改自身的 props。这是规则,必须要遵守!

 

2.State

 
在 hooks 出现之前,只有 class 组件才有 state 功能,针对 hooks (函数组件专用),后面会专门的章节,现在主要使用 class 组件的 this.setState()this.state 来说明:

import React from 'react';

class ClassComp extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      count: 0,
    }
  }
  increase = () => {
    const { count } = this.state;
    this.setState({ count: count + 1 });
  }

  render () {
    const { count } = this.state;
    return (
      <>
        <div>{count}</div>
        <button onClick={this.increase}>点击</button>
      </>
    )
  }
} 

export default ClassComp;

 
上面代码中,就是一个简单的增加计数的例子。

 

2.1 不要直接修改 State

 

this.state.count = 100;

这样的修改方式是错误的,react 是通过 Object.is() 来 进行新旧 state 的比较,这样直接去修改,会导致 Object.is() 判断新旧 state 中的 count 相等,从而导致组件不会被重新渲染

 

2.2 State 的更新可能是异步的

 
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

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

 
这样点击一次之后,count 是变成 3 吗?

 
当然我这样问了,很多同学就会发现这是一个坑,没错,正确答案是:1 。

 
明明调用了三次 this.setState() ,为什么会这样呢,这就回到了本小节一开始说到的:出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

 
虽然你点击之后,调用了三次 setState(),但是实际上由于合并调用,这样连续三次调用时的 state 其实都是0,因此每次增加都是 0 + 1,自然最后结果为 1.

 
难道就不能依赖他们的值来更新下一个状态吗?

 
那么如果在某些情况下,我必须要依赖上一个 state 怎么办?这也是有解决办法的,考虑下面代码:

increase = () => {
  const { count } = this.state;
  this.setState(state => ({ // 此时 state.count = 0
    count: state.count + 1,
  }));
  this.setState(state => ({ // 此时 state.count = 1
    count: state.count + 1,
  }));
  this.setState(state => ({ // 此时 state.count = 2
    count: state.count + 1,
  }));
}

 
这时候会发现,每次点击增加按钮,值增加 3。这是为什么呢?

 
这是因为,我们在调用 this.setState() 的时候,不再传入一个对象,而是传入一个函数,这个函数的参数保证是最新的state,这样在每次增加时,都是是用最新的 state 里面的值来做加数。

 
注1:this.setState() 传入函数的参数实际有两个,第一个参数是最新的 state,第二个参数最新的 props

 
注2:其他可及时获取state更改后的值的方式(同步)

 
1.通过在setState中传入第二个参数(函数),setState 是同步

this.setState({
  count: this.state.count + 1,
}, () => {
  console.log(this.state.count);	// 同步
});

 
2. setTimeout 中 setState 是同步

setTimeout(() => {
  this.setState({
    count: this.state.count + 1,
  });
  console.log(this.state.count);	// 同步
}, 0);

 
3.自己定义的 DOM 事件, setState 是同步

componentDidMount = () => {
  document.body.addEventListener('click', this.handleClick);
}

// 组件销毁时执行
componentWillUnmount = () => {
  // 即是销毁自定义的DOM事件
  document.body.removeEventListener('click', this.handleClick);
}

handleClick = () => {
  this.setState({
    count: this.statecount + 1,
  });
  console.log(this.state.count);
}

 
在这里记得要销毁自定义的 DOM 事件,包括上面使用 setTimeout ,也需要在 componentWillUnmount 中执行 clearTimeout。

 

2.3 State 的更新会被合并

 
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。(当传入的是函数时,则不会合并,如2.2中例子)

 
考虑下面代码:

// 省略部分代码
constructor (props) {
  super(props);
  this.state = {
    count: 0,
    total: 100,
  }
}

increase = () => {
  const { count } = this.state;
  this.setState({
    count: count + 1,
  });
}

this.state 中的 total 值为100,在 increase 函数中,我们增加 count 的值,这时候 React 会完整保留this.state.total,而完全替换了 this.state.count

 
注:这里的合并是浅合并,因此即使 this.state 中的值是引用类型,在替换时实际上是替换了地址。

 

3.数据流是向下移动的

 
React 数据是自上而下的,或者说是单向的。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。

 
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。

 
组件可以选择把它的 state 作为 props 向下传递到它的子组件中:

<h1>{this.state.count}</h1>

 
对于自定义组件同样适用:

<selfComp count={this.state.count} />

 
每个组件都是真正独立的(引自: React 数据是向下流动的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值