前端宝典之一:React基础内容jsx、state、props深度解析

前言—react与vue对比

相同点

1、都使用虚拟DOM来提高渲染效率。

2、都支持服务端渲染(SSR)。

3、都有响应式编程的概念,能够自动更新DOM。

4、都可以通过组合或继承的方式创建组件。

5、都有支持的社区和丰富的资源。

不同点

1、vue : mvvm模式,依赖追踪模式
2、react单向数据流,ui = render(data)
props 只读属性,父向子流动,使用state、setState更新数据
3、运行时和编译时差别

  • vue:
    (1)编译时 对templatejsx解析,对使用受限制的语法v-if/v-for编译时转换,vue3可以做到按需编译
    (2)运行时 diff优化
  • react:
    侧重运行时 jsx编译成js createElement,此时做diff比较(fiber框架针对diff掉帧做的优化)

4、数据是否可变
React immutable不可变,不可通过this.state = xxx来修改,只能通过setstate
Vue是数据mutable可变,响应式数据双向绑定
5、语法不同
React:只有jsx语法
Vue:sfc(template、js、css)也有jsx

一、React简介

React 是一个用于构建用户界面的 JavaScript 库。把用户界面抽象成一个个的组件,通过引入jsx语法,复用组件更容易,同时保证组件结构清晰。

React 特点

1. 专注于视图层

React不是完成的MVC/MVVM框架,很多人认为 React 是 MVC 中的 V(视图),但是你也可以把react理解为MVC模式
在这里插入图片描述

  • controller中注册model和view
  • view向model注册
  • model更新数值通知view

2. Vitual DOM

React把真实的DOM树转换成js对象树,也就是Vitual DOM,通过对DOM的模拟,最大限度地减少与DOM的交互,因此更加高效。
在这里插入图片描述
每次数据更新后,重新计算Vitual DOM,并和上一次生成的Vitual DOM做对比,对发生变化的部分批量更新。
React类组件也提供了直观的shouldComponentUpdate生命周期回调,函数式组件提供了useEffect,来减少数据变化后不必要的Vitual DOM对比过程。

3. 函数编程

React采用声明范式,可以轻松描述应用。充分利用函数式方法减少冗余代码,此外,本身就是简单函数,易于测试。函数式编程才是React精髓。声明式和命令式不同,声明式侧重于结果,而命令式侧重过程。

4. 单向响应的数据流

React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单

5. 组件

通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。

6. 灵活

React可以与已知的库或框架很好地配合。

二、JSX语法

React 使用 JSX 来替代常规的 JavaScript。
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
我们不需要一定使用 JSX,但它有以下优点:

  • (1)JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • (2)它是类型安全的,在编译过程中就能发现错误。
  • (3)使用 JSX 编写模板更加简单快速。

1、使用JSX基础

1.1. XML语法

JSX是将ui和逻辑层耦合在组件里,使用{}标识
类XML语法的ECMAScript扩展,使用XML好处是标签可以任意嵌套,可以像写HTML一样清晰的看到DOM树状结构及其属性。

const list = () => {
	<div>
		<Title>这是标题</Title>
		<ul>
			<li>列表item</li>
			<li>列表item</li>
			<li>列表item</li>
		</ul>
	</div>
}

上面构造了一个list组件,注意事项:

  • 定义标签时,只允许被一个标签包裹,例如:
const list = () => {
  	<Title>这是标题</Title>
  	<ul>
  		<li>列表item</li>
  		<li>列表item</li>
  		<li>列表item</li>
  	</ul>
}

这样就会报错了,原因是没有最外层包裹,无法转译成功。

  • 标签一定要闭合
    当然,JSX报错机制很强大,如果有拼写错误会直接打印出来。

1.2. 元素类型

在构建页面中有两种元素:DOM元素和组件元素,在JSX中会有对应,对应规则是标签的首字母是否是小写字母,小写首字母对应DOM元素,大写首字母对应组件元素。

1.3. 注释

注释需要写在花括号中,实例如下:

const list = () => {
	<div>
		<Title>这是标题</Title>
		{/*注释...*/}
		<ul>
			<li>列表item</li>
			<li>列表item</li>
			<li>列表item</li>
		</ul>
	</div>
}

1.4. 元素属性

JSX语法中DOM元素属性有两个例外,class和for

  • class属性改为className
return (
        <div>
            <div className="hot-box">
                <ul className="hot-list">
                    <SubThree/>
                </ul>
            </div>
        </div>
    )
  • for属性改为html-for

2、jsx=>babel编译全流程

React.createElement 函数用于创建一个虚拟DOM(也称为元素)节点,而 render 函数负责将虚拟DOM渲染到真实的DOM中。

// 在React中,jsx=>babel编译全流程:
// 这里是一个简单的例子:
// 引入React和ReactDOM
import React from 'react';
import ReactDOM from 'react-dom';
 
// 使用React.createElement创建一个虚拟DOM元素
const element = React.createElement('h1', {className: 'greeting'}, 'Hello, world!');
// React.createElement包含type/props/children
 
// 使用ReactDOM.render将虚拟DOM元素渲染到页面的某个部分即实际dom
ReactDOM.render(element, document.getElementById('root'));
// 二次更改虚拟dom时,通过diff算法,比对虚拟dom,触发更新

3、 flushSync强制同步

调用 flushSync 强制 React 刷新所有挂起的工作,并同步更新 DOM。
react18版本后setState方法都是异步的,同一动作内多个setState会被批处理
如果想在18以后版本内使用 setState 同步,可以使用flushSync
但是使用 flushSync 是不常见的行为,并且可能损伤应用程序的性能,尽量不要使用

import { flushSync } from 'react-dom';

flushSync(() => {
  setSomething(123);
});

// 类组件内如何拿到 setState 的值,进行下一个 setState 的更新?
this.setState((state) => {
    count: state.count + 1
})

三、React组件

React组件就是组件元素,组件元素被描绘成纯粹的JSON对象,意味着可以使用方法或类来进行构建。
React组件由三部分构成–属性(props)、状态(state)以及生命周期方法。如下图:
在这里插入图片描述

React组件构建方法

组件首字母大写,会被当作变量渲染成dom,否则被当成字符串编译。
react三大组件:类组件、函数式组件和高阶组件
其中高阶组件HOC(higher ordered component)在进阶篇中介绍

1、 类组件

import React from 'react';
 
class ClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
 
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}
 
export default ClassComponent;

2、函数式组件

import React, { useState } from 'react';
 
function FunctionalComponent() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
 
export default FunctionalComponent;

四、React数据流

在React中,数据是自顶向下单向流动的,即从父组件到子组件。
stateprops是React组件中最重要的概念。如果顶层组件初始化props,那么React会向下遍历整颗组件树,重新尝试渲染所有相关的子组件。而state只关心每个组件内部状态,这些状态只能在组件内改变。把组件看成一个函数,那么props作为参数,内部state作为内部参数,返回一个Vitual DOM的实现。

1、state

1.1 state基础

  • React中state用于管理组件内部状态
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
 
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
 
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
 
  tick() {
    this.setState({
      date: new Date()
    });
  }
 
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  • setState是一个异步方法,一个生命周期内所有的setState方法会合并操作。
  • 我们并不推荐滥用state,过多的状态会让数据流混乱,程序变得难以维护。

1.2 state更新方式

(1)函数式组件

在函数式组件中,setState 是通过使用 React 的 useState Hook 获取的。useState 返回一个数组,其中包含当前状态值和一个用于更新该状态的函数。你可以通过解构赋值获取这两个值。

以下是一个简单的例子,展示了如何在函数式组件中使用 setState

import React, { useState } from 'react';
 
function ExampleComponent() {
  const [count, setCount] = useState(0);
 
  const incrementCount = () => {
    // 获取当前 count 的值,并加一
    setCount(count + 1);
  };
 
  const decrementCount = () => {
    // 获取当前 count 的值,并减一
    setCount(count - 1);
  };
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
      <button onClick={decrementCount}>Decrement</button>
    </div>
  );
}
 
export default ExampleComponent;

在上面的代码中,incrementCountdecrementCount 函数在点击按钮时被调用,并且它们使用当前的 count 值来计算下一个状态值。这是因为 count 是函数组件的本地状态,并且每次状态更新后都会重新渲染组件。

(2)类组件

可以通过this.state来访问setState的值,但要注意的是,setState是异步的,所以如果你需要基于一个状态更新另一个状态,你应该使用setState的回调函数形式,该函数会在状态更新完成后被调用,并且新的状态值会作为参数传入。下面是一个例子:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0,
      doubleValue: 0,
    };
  }
 
  // 假设我们要将value状态值翻倍,并更新doubleValue状态
  updateDoubleValue = () => {
    this.setState(
      { value: this.state.value + 1 },
      () => {
        this.setState({ doubleValue: this.state.value * 2 });
      }
    );
  }
 
  render() {
    return (
      <div>
        <button onClick={this.updateDoubleValue}>Update Double Value</button>
        <p>Value: {this.state.value}</p>
        <p>Double Value: {this.state.doubleValue}</p>
      </div>
    );
  }
}

在上面的例子中,我们有一个MyComponent类组件,它有两个状态:valuedoubleValue。我们定义了一个方法updateDoubleValue,在这个方法中,我们首先通过setStatevalue的值增加1,然后在setState的回调函数中,我们根据更新后的value值(此时等于新的状态值),计算出doubleValue的新值,并用setState进行更新。

1.3 setState版本变更

  • v18之前
    (1)在组件生命周期或者react合成事件中setState都是异步的
    (2)setTimeout或者原生dom事件setState是同步的
  • v18之后
    都是异步的
    多个setState会合并处理

2、props

React是单向数据流,主要的流通管道就是props。
所有react组件都要像纯函数一样去保护props不被更改
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。

2.1 props父向子传值

function HelloMessage(props) {
    return <h1>Hello {props.name}!</h1>;
}
 
const element = <HelloMessage name="Runoob"/>;

由于自定义组件的参数是可以自定义的,在上面代码中父组件中使用子组件HelloMessage,参数name传值,子组件使用props.name拿到参数值

2.2 用function prop向父组件通信

首先我们创建一个tab的子组件作为例子

import React, {Component} from 'react';
import { Tabs, WhiteSpace, Badge } from 'antd-mobile';
import 'antd-mobile/dist/antd-mobile.css';

const tabs = [
    { title: '今日推荐', sub: '1' },
    { title: '福利', sub: '2' },
    { title: '热点', sub: '3' },
    { title: '信用卡', sub: '4`' },
];

class TheTab extends Component {
    constructor(props) {
        super(props)
    }
    handleClick (e)=> {
        this.props.onClick(e.sub)
    }
    render() {
        return (
            <div>
                <Tabs tabs={tabs}
                      initialPage={0}
                      onChange={this.handleClick}
                >
                </Tabs>

                <WhiteSpace />
            </div>
        )
    }
}

export default TheTab;

当用户点击tab的item,会触发handleClick方法,在handleClick方法中通过prop调用父组件的onClick方法,并且传递参数。

这时我们看一下父组件中的部分代码:

handleClick = (id) =>{
    console.log(id)
    this.setState({
        tabId: id
    })
}
render() {
    return (
        <div className="App">
            <TheTab onClick={this.handleClick}/>
        </div>
    )
}

通过触发父组件的onClick方法就实现了对tabId值的修改。·

五、React生命周期

React生命周期可以分为挂载、渲染和卸载几个阶段。当渲染后的组件需要更新时会重新渲染组件,直至卸载。

生命周期分为两类:

  • 当组件挂载和卸载时
  • 当组件接受新数据,即组件更新时

1、组件挂载时

	componentWillMount() 
		let httpUrl = '******'
        this.getData(httpUrl);
    }

    componentDidMount() {}

componentWillMount 在render之前执行,初始化的state一般不会在这里setState,因为组件更新state但只渲染一次,没有意义,初始化的state都可以放在this.state中。

componentDidMount在render之后执行,如果在这里执行setState方法,组件会再次更新,那么在初始化过程中就两次渲染了组件,这不是一件好事。
但是:
实际情况是,一些场景不得不setState,如计算组件的位置和宽高,不得不先渲染组件,更新部分数据后再次渲染。

2、组件卸载时

componentWillUnMount() {}

在这个生命周期,往往执行一些清理操作,如事件回收和清理定时器。

3、数据更新过程

更新过程指的是,父组件向下传递props或组件自身执行setState方法时执行的一系列动作。

improt React, {Component, PropTypes} from 'react';
class App extends Component{
	  componentWillReceiveProps(newProps) {
        console.log('Component WILL RECEIVE PROPS!')
	  }
	  shouldComponentUpdate(newProps, newState) {
	        return true;
	  }
	  componentWillUpdate(nextProps, nextState) {
	        console.log('Component WILL UPDATE!');
	  }
	  componentDidUpdate(prevProps, prevState) {
	        console.log('Component DID UPDATE!')
	  }
	 render() {
	  	return <div>this is a demo </div>;
	 }
}

如果组件自身的state更新了,会依次执行componentWillReceiveProps、 componentWillUpdate、 render 和 componentDidUpdate

这几个生命周期要注意的点有:

3.1 shouldComponentUpdate判断更新

shouldComponentUpdate是一个特别的方法,它接受需要更新的props和state,让开发者增加条件判断,需要时更新,不需要时不更新。因此,当方法返回false时,组件不再向下执行生命周期方法

正确的组件渲染,是性能优化的手段之一。

3.2 无状态组件

无状态组件没有生命周期

3.3 componentWillUpdate

不能在componentWillUpdate中执行setState

3.4 componentWillReceiveProps

如果在父组件传入props而更新的,componentWillReceiveProps先执行,此方法可以作为React在props传入后,渲染之前setState的机会,在此方法中调用setState是不会二次渲染的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值