一、基本使用

(一)React条件渲染

  • 在vue中,通过指令来控制:比如 v-if、v-show
  • 在React中,所有的条件判断都和普通的js代码一致

常见的条件渲染的方式有:

方式一:条件判断语句

适合逻辑较多的情况。
React 知识点整理_ide

方式二:三元运算符

  • 适合逻辑比较简单
    React 知识点整理_Vue_02

方式三:与运算符 &&

  • 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不u渲染
    React 知识点整理_javascript_03

方式四:v-show 的效果

  • 主要是控制display属性是否为none
    React 知识点整理_ide_04

(二)React 列表渲染

在真实的开发中,我们会从服务器请求到大量的数据,数据会以列表的形式存储。
在React中并没有像Vue模块语法中的v-for指令,而是需要我们通过js代码的方式组织数据,转成jsx。

  • 很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加简洁明了。
  • 但是React中的JSX正是因为和js无缝衔接,让它可以更加灵活。
  • React 是真正可以提高我们编写代码能力的一种方式。
    如何展示列表?
    使用数组的map高阶函数。

在展示一个数组中的数据之前,需要先对它进行一些处理。

  • 比如过滤掉一些内容:filter函数
    React 知识点整理_react.js_05
  • 比如截取数组中的一部分内容:slice函数
    React 知识点整理_react.js_06
    对比:
    列表渲染(map函数、key)。对应Vue的v-for。
// React
state = {
	list: ["list1","list2"]
}
render(){
// 在{函数体}中返回一个li的list
	return (
		<ul>
			{this.state.list.map(item => <li key={item}>{item}</li>)}
		</ul>
	)
}
// Vue
<ul>
	<li v-for="item in list" :key="item">{{item}}</li>
</ul>

可以看出,二者比较相似,只是:

  • React 提供了简单的jsx语法,具体实现需要js的能力。内部没有再提供指令,简洁上手。
  • Vue提供了很多指令,如v-if、v-for这种。用之前需要先了解指令的相关用法,日常使用会有一定便捷。

(三)事件-event

事件写法:onXxxx,如onClick。Vue中是@click、v-on:click

  1. 事件调用方法时的this
    • bind(this)
    • 箭头函数
class MyComponent extends React.Component{
	constructor(props){
		super(props)
		this.state = {
			text: '模版插值'
		}
		// bind(this)
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick = function(){
		this.setState({
			text: '修改text'
		})
	}
	// 箭头函数
	hanleArrowClick = () => {
		this.setState({
			text: '修改text from handleStaticClick'	
		})
	}
	render(){
		return(
			<div>
				<p>{this.state.text}</p>
				<!-- bind(this)也可以在这里写,但是写在constructor性能更佳 -->
				<button onClick={this.handleClick}>change</button>
				<!-- 这里用的静态方法。静态方法this永远指向该实例 -->
				<button onClick={this.handleArrowClick}>change</button>
			</div>
		)
	}
}

分析理解:

  • React的事件不是真实绑定在dom上。并且事件调用并不是发生在组件层,而是发生在最外层的事件监听器(事件委托)。
  • 而js的this关键字是运行时确定的,在函数被调用时才绑定执行环境(谁调用就指向谁)。所以事件中需要绑定this;或者使用箭头函数写法(定义时就绑定this指向当前组件)。
  1. event 参数(Vue的event是原生事件)
  • event 并不是原生事件,而是React封装的组合事件。
handleClick = function(event){
	// react的合成事件
	console.log('reactEvent', event);
	// 原生事件
	console.log('nativeEvent',event.nativeEvent.target);
	// 指向 document
	console.log('nativeEvent',event.nativeEvent.currentTarget);
}

React 知识点整理_数据_07

  • 模拟出了DOM的所有能力。如 event.preventDefault()、event.stopPropagation()
  • 所有事件都绑定到document上(React17之前)
  • 所有事件都绑定到root组件(React17)
  1. 传递自定义参数
  • 正常使用的方法传递即可,注意:event 参数会追加到最后一位。
handleClick = function (param, event){
	console.log(param,event);
}

(四)受控组件

  • 受控组件简单理解:表单的值,受到state的控制。
  • state驱动试图渲染(可对应高级特性中的非受控组件进行理解)。如表单实现双向绑定。对应Vue的v-model
class MyComponent extends React.Component{
	constructor(props){
		super(props)
		this.state = {
			inputVal: 'input'
		}
	}
	render(){
		return (
			<div>
				<p>{this.state.inputVal}</p>
				<input type="text" value={this.state.inputVal} onInput={this.handleInput} />
			</div>
		)
	}
	// 需要手动实现数据变更(比 v-model 多了一步)
	handleInput = e => {
		this.setState({
			inputVal: e.target.value;
		})
	}
}

(五)父子组件通信

  1. 传递数据和接收数据。对应Vue是v-bind和接收props。
  • 传递数据:
  • 接收数据:
class ChildComponent extends React.Component{
	constructor (props){
		super(props)
	}
	render (){
		return <div>
			{this.props.count}
		</div>
	}
} 
  1. 传递函数。对应Vue的@xxx=xxx、this.$emit(‘xxx’)
// 父组件
class MyComponent extends React.Component {
	constructor (props){
		super(props)
		this.state = {
			count: 99;
		}
	}
	render(){
		return (
			<div>
				<ChildComponent count={this.state.count} changeCount={this.changeCount} />
			</div>
		)
	}
	// 接收子组件传递的count来修改自身的count。
	changeCount = count => {
		this.setState ({
			count
		})
	}
}
// 子组件
class ChildComponent extends React.Component{
	constructor(props){
		super(props)
	}
	render(){
		return <div>
			{this.props.count}
			<button onClick={this.addCount}>addCount</button>
		</div>
	}
	addCount = () => {
		// 获取父组件传递的props
		const {changeCount,count} = this.props
		// 调用父组件的changeCount方法
		changeCount(count + 1);
	}
}

(六)setState

  1. 不可变值
  • class 组件使用setState修改值。
// 修改基础类型数据
this.setState({
	// 保证原有值不变,不能直接操作state的值(不能写成this.state.count++)
	// this.state.count + 1 是一个新的结果 对
	// this.state.count++;   ❌
	count: this.state.count + 1;
})
// 修改引用类型 - 数组
this.setState({
	// 避免使用push、pop 等直接修改原数组的方法
	list: [...this.state.list,3],  // 新增一个值
	list2: this.state.list.slice(1) // 截取
})
// 修改引用类型 - 对象
this.setState({
	// 避免直接在原对象上做修改
	obj: {...this.state.obj, name: 'shancai'}
})

实际开发中,可能会遇到深层对象的情况需要setState,可以使用immer、immutable这种不可变值的库。

  1. setState 是同步执行还是异步执行?
  • 分两种情况:
    • 正常用法是异步执行;
    • 在setTimeout和自定义DOM事件的cb中是同步执行。
// 直接用setState是异步的,这里console.log 拿不到修改后的值。
this.setState({
	count: this.state.count + 1;
})
// 同步 console 拿不到修改后的值(如果需要拿到修改后的值,可以在setState中添加回调)
console.log(this.state.count)

// 在 setTimeout中用setState是同步的
setTimeout(()=>{
	this.setState({
		count: this.state.count + 1;
	})
	// 这里能拿到最新的结果
	console.log(this.state.count + 1)
}) 
// 在自定义的DOM事件用setState也是同步
componentDidMount(){
	// 绑定body的点击事件
	document.body.addEventListener('click',()=>{
		this.setState({
			count: this.state.count + 1;
		})
		// 能拿到最新结果
		console.log(this.state.count)
	})
}
  1. setState 是否会被合并执行
  • 两种情况:

    1. 合并执行——传入对象(注意:如果在setTimeout的回调中是同步执行,也就不存在合并执行的说法了)
// 常规用法时,setState 异步执行,结果只加1(被合并执行)
this.setState({
	count: this.state.count + 1;
})
this.setState({
	count: this.state.count + 1;
})
  1. 不合并 执行——传入函数
// 结果加2,分别都执行
this.setState((prevState)=>{
	return {
		count: prevState.count + 1
	}
})
this.setState((prevState)=>{
	return {
		count: prevState.count + 1
	}
})

(七)生命周期(对比Vue)

  • React
    • constructor 类似Vue的init阶段(initLifeCycle、initState…)
    • render类似Vue的$mount阶段(执行render -> 得到VNode -> patch到DOM上)
    • componentDidMount 等价Vue的mounted
    • 在组件更新阶段,render之前还有一个shouldComponentUpdate的阶段(能控制本次组件是否需要render),可以在该阶段做一些性能优化。
      React 知识点整理_数据_08
二、高级特性

(一)非受控组件

  • state 不控制视图渲染。(理解:数据不受组件state的控制,直接跟DOM相关)
  • 使用场景:必须要操作DOM,setState不能满足。比如input的type=file时的一些交互。
class MyComponent extends React.Component{
	constructor (props){
		super(props)
		this.state = {
			inputText: ''
		}
		// 获取DOM,对应Vue的ref
		this.textInput = React.createRef();
	}
	render(){
		return (
			<div>
				<!-- 对应Vue中的ref,但是Vue传入的是个字符串 -->
				<input type="text" ref={this.textInput} defaultValue={this.state.inputText} />
			</div>
		)
	}
	handleClick = function (){
		// 直接获取DOM的值
		console.log(this.textInput.current.value)
	}
}

(二)React Portals

  • 让组件渲染到父组件以外,对应Vue3的teleport
    • 场景:比如日常开发中,父组件设置overflow:hidden的布局。导致组件内的一些弹窗、下拉菜单(绝对定位)等被切割的问题,这时需要逃离父组件的嵌套。
return ReactDOM.createPortal (
	<div>
		<p>子组件挂载到body层,逃离父组件</p>
	</div>
	document.body
)

(三)React Context

  • 两个核心:1.数据创建:provider; 2.数据消费:consumer
  • 具有数据透传的能力,子孙组件能拿到最外层组件的数据。有点类似Vue的v-bind=“$attrs”
// MyContext 文件
import {createContext} from 'react'
export default createContext()

// 外层组件 Provider
import MyContext from './MyContext'
class MyComponent extends React.Component{
	constructor(props){
		super(props)
		this.state = {
			val: 'provoiderVal'
		}
	}
	render(){
		return (
			// 设置 value值
			<MyContext.Provider value={this.state.val}>
				<div>
					<!-- Second 子组件 -->
					<Second />
				</div>
			</MyContext.Provider>
		)
	}
}

// 子组件Consumer - class Component
import MyContext from './MyContext'
import Third from './Third'
export default class Second extends React.Component{
	// 通过静态属性 contextType 接MyContext
	static contextType = MyContext
	// 通过 this.context 访问
	render(){
		return (
			<div>
				{this.context}
				<!-- Second组件的子组件 -->
				<Third />
			</div>
		)
	}
}
// 孙组件Consumer——function Component
import MyContext from "./MyContext"
import {useContext} from 'react';
export default function Third (){
	// 使用useContext获取
	const context = useContext(MyContext)
	return (
		<div>{context}</div>
	)
}

(四)异步组件

  • 两个核心: lazy + Suspense。跟Vue的异步组件很相似。
// Vue中的异步组件——工厂函数
Vue.component('async-component', function (resolve) {
    require(['./async-component'], resolve) 
})
// Vue中的异步组件——Promise
Vue.component(
  'async-component',
  () => import('./async-component') 
)

// React的异步组件
// lazy接收一个Promise,并返回一个新的React组件
const Second = React.lazy(() => import('./Second'))
class MyComponent extends React.Component{
  constructor(props) {
    super(props)
    this.state = {
      val: 'providerVal'
    }
  }
  render() {
    return (
      <MyContext.Provider value={this.state.val}>
        <div>
          <!-- fallback为必传属性(未加载完时显示),接收一个组件 -->
          <React.Suspense fallback={<div>loading...</div>}>
            <Second />
          </React.Suspense>
        </div>
      </MyContext.Provider>
    )
  }
}

(五)shouldComponentUpdate

  1. React 的性能优化的重点。SCU是组件update前的一个生命周期,可控制组件更新
  2. 默认返回true(组件每次都会执行render)。可通过自定义逻辑(如对比数据变化判断是否需要更新),返回false对组件进行性能优化。
  3. 基本用法:
class MyComponent extends React.Component{
  constructor(props) {
    super(props)   
  }
  shouldComponentUpdate (nextProps, nextState, nextContext) {
    console.log(nextState.count, this.state.count)
    if (nextState.count !== this.state.count) {
      return true // 允许渲染,会往下执行render
    }
    return false // 不渲染,不执行render
  }
  render() {
    return ...
  }
}
  1. 为什么需要这个scu这个生命周期优化
  • React的组件更新逻辑:父组件更新,子组件无条件更新。对比Vue,这点有很大区别。
    Vue的数据是收集对应组件的渲染Watcher的,也就是行成了一个对应关系。只有当前组件依赖的值发生改变才会出发组件更新。
  • 如以下代码,父组件点击按钮会修改count值触发父组件更新。如果不设置SCU,则父组件更新会导致子组件ShowText更新。观察可知,子组件接收的props并未被修改,其实是不需要重新render的。所以,SCU的优化场景就出现了。
// 父组件
class MyComponent extends React.Component{
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      text: 'children Component'
    }
  }
  render() {
    return (
      <div>
          <p>{this.state.count}</p>
          <button onClick={this.handleClick}>add count</button>
          <!-- 注意子组件接收的text并未发生改变 -->
          <ShowCount count={this.state.text} />
        </div>
    )
  }
  handleClick = function () {
    // 父组件点击修改Count
    this.setState({
      count: this.state.count + 1
    })
  }
}

// 子组件
class ShowText extends React.Component{
  constructor(props) {
    super(props)
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('is update')
  }
  shouldComponentUpdate (nextProps, nextState, nextContext) {
    // 判断props的text是否改变
    if (nextProps.text !== this.props.text) {
      return true // 允许渲染,会往下执行render
    }
    return false // 不渲染,不执行render
  }
  render() {
    return (
      <div>
        <p>{this.props.text}</p>
      </div>
    );
  }
}
  • 思考:为什么React不在内部实现SCU,而是提供了个生命周期出来改开发者自行实现?
    1. 不是所有场景都有必要做性能优化,出现卡顿时再做性能优化是比较合理的开发模式。如果所有情况都直接在SCU做深度比对state,这样本来就会造成性能损耗,往往是不必要的。
    2. SCU一定要配合不可变值来用。如果内部实现SCU深度比对state再判断是否更新组件,且不遵循不可变值填写可能会造成组件更新失败问题。因为不排除有不遵循不可变值写法的开发者。比如 this.state.list.push(3),再写this.setState({list: this.state.list}) 这种情况。
  1. PureComponent —— 实现了浅比较的SCU