自学react基础

本文深入探讨React中的JSX语法、函数式组件与类组件的使用,包括虚拟DOM、状态管理和生命周期。同时,讲解了组件通信、PureComponent、Hooks以及错误边界等优化策略,最后提到了React的懒加载和数组处理注意事项。
摘要由CSDN通过智能技术生成

JSX

js拓展(语法糖) 使用jsx代替document.createElement ,babel将JSX转换为js

语法规则:
1. 定义虚拟DOM时,不用document.createElement
2. 可以在JSX中写js表达式,想在JSX中使用js表达式,需要用{}来包裹
3. 样式类名指定不用class,用className, JSX的标签属性需要小驼峰方式
4. 只能有一个根标签
5. 标签必须闭合
6. 自定义组件的首写字母必须大写
优点:更好的创建节点,使得代码一目了然

虚拟DOM:

虚拟DOM是一个一般对象,有真实DOM的部分对象,真实DOM,有真实DOM的所有对象 。虚拟DOM最终会被react转换为真实DOM展现在页面上

函数式组件

函数式组件中的this指向undefined (因为 bebel开启了严格模式)

 //创建函数式组件
        function Demo(){
            return <h1>简单的函数式组件</h1>
        }
    
       /*
        渲染到页面中 
         1. 自定义组件渲染需要用标签形式
         2. 自定义组件首字母需要大写
         3. 标签必须闭合
		*/
        ReactDOM.render(<Demo />, document.getElementById('test'))
    
    	/*
        执行了reactDOM.render之后发生了什么
        	1. React解析组件标签,找到了Dome组件
        	2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现到页面中
		*/

类式组件

类的相关复习

  1. 类的构造器不是必须写的,要对实例进行一些初始化操作,如添加指定属性时才写
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须写的
  3. 类中所定义的方法,都是放在了类的原型对象上,供实例对象使用

类式组件

  //创建类式组件
    class Dome extends React.Component{
        render(){
            return  
        }
    }

    //渲染到页面
    ReactDOM.render(<Dome />, document.getElementById('test'))

	/*
    执行了reactDOM.render之后发生了什么
    	1. React解析组件标签,找到了Dome组件
	    2. 发现组件是使用类定义的,随后new出来该类的实例对象,并通过该实例调用原型上的render函数
    	3. 将返回的虚拟DOM转为真实DOM,随后呈现到页面中
    */

简单组件 & 复杂组件

复杂组件 有状态 state 的组件叫做复杂组件

接受props & 自定义state

    class Demo extends React.Component{
        constructor(props){
            //🚩在继承React.Component之后,如何接收到React.component上的属性
            //如果super中不接受props,并且写了super,则this.props = undefined
            super(props)
            //🚩如何自定义Demo原型上的属性🚩
            this.state = {}
        }
        render(){return <h1> 复杂组件 </h1>
        }
    }

类中this指向问题

    class Demo extends React.Component{
        
        myFunction(){
            //🚩 类中所定义的方法已经在局部开好了严格模式
            //🚩 如果不是通过实例调用的方式 调用,则,该方法中的this指向undefined,而不是window
            console.log(this.state)
        }
        render(){
        	/*
            	🚩 在这里并没有调用myFunction函数,而是顺着Demo的原型找到了myFunction函数,
            	🚩🚩 再将myFunction交给onClick作为回调 🚩🚩
            
            	以下代码相当于 
             	let a = this.myFunction 
             	onClick={ a }
             	不是实例调用,而是直接调用(如下一个代码块所示同理)
            */
            return <h1 onClick={ this.myFunction }> 复杂组件 </h1> 
        }
    }
    class Student{
        //开启局部严格模式
       stude(){
        console.log(this)//this指向undefined
       }
    }
    let s1 = new Student()
    s1.study() //通过实例调用study方法,结果为s1实例
    let x = s1.study
    x() //因为开启了局部严格模式,则,这里的this为undefined,结果为undefined

解决类中this只想问题

 class Demo extends React.Component{
           constructor(props){
               super(props)
               
           /*
               🚩 this指向Demo 的实例对象
               🚩 Demo 的实例对象上没有myFunction方法,
               🚩 myFunction方法在Demo 构造函数的原型上,
               🚩 this通过原型链可以找得到myFunction方法,但是this丢失
               🚩 再通过bind函数将原型链上的this指向Demo 实例对象
               🚩 bind函数返回一个函数
        	*/ 
               this.myFunction = this.myFunction.bind(this)
               				--------------------------------
           }
           
           myFunction(){
               console.log(this)
           }
           render(){       
               return <h1 onClick={ this.myFunction }> 复杂组件 </h1> 
           }
       }
 class Demo extends React.Component{
           
           /*
          		🚩 类中可以直接写赋值语句  a = 1 , 但是类中不可以直接定义变量 let a = 1
           		🚩 直接将state写在类中,而不是constructor中
            	🚩 这样写的state是在实例对象上,而不是构造函数的原型上 
           */ 
           state = {
               a: 1,
           }
       
       ------------------------------------------------------------------------------
          
       	 /*
       	 赋值:
       	 	🚩 以下的myFunction在实例对象上,而不是在类的原型对象上,
       	 	🚩 this指向实例对象,使用该方法解决this指向问题
       	*/
           myFunction = () => {
               console.log(this)
           }
           /* 
           	定义:
          		🚩 以下的myFunction在原型对象上,而不是在类的实例对象上,
          		🚩 如果不是实例调用的话,则this指向undefined
           */
           myFunction() {
               console.log(this)
           }
           
           render(){       
               return <h1 onClick={ this.myFunction }> 复杂组件 </h1> 
           }
       }

props传参校验

下载: yarn add prop-type

引入: import PropTypes from ‘prop-types’

使用:

  class Demo extends React.Component{
        // 添加传参校验
        static propTypes = {
            addTodo:PropTypes.func.isRequired,
            name:PropTypes.string
        }
    	//默认值
    	static defaultProps = {
            name:'小明'
        }
    }

refs

  1. 字符串形式的ref (过时,存在问题,效率不高,未来可能移除)

 class Demo extends React.Component{
        handleClick = () =>{
            console.log(this.refs.input1)
        }
        render(){
            return 
            	<div>
                	<inpute ref="input1" type="text" placeholder="点击按钮提示数据" />
                    <button onClick={ this.handleClick }> 按钮 </button>
                </div>
        }
    }
  1. 回调函数形式的ref (更新时,ref传参两次)

内联函数定义的方法在更新过程中会执行两次,第一次传入参数null,第二次会传入DOM元素。这是因为在每次渲染的时候会创建一个新的函数实例,所以react清空旧的ref并且设置新的,通过将ref的回调函数定义成class的绑定函数的方式(第三种方式)可以避免上述问题,但是在大多数情况下,他是无关紧要的

   class Demo extends React.Component{
        handleClick = () =>{
            console.log(this.input)
        }
        render(){
            return 
            	<div>
                	<inpute ref={ e => this.input = e } type="text" placeholder="点击按钮提示数据" />
                    <button onClick={ this.handleClick }> 按钮 </button>
                </div>
        }
    }
  1. ref定义成class的绑定函数 (传参一次)

   class Demo extends React.Component{
        handleClick = () =>{
            console.log(this.input)
        }
        saveRef = (e) => {
            this.input = e
        }
        render(){
            return 
            	<div>
                	<inpute ref={ this.saveRef } type="text" placeholder="点击按钮提示数据" />
                    <button onClick={ this.handleClick }> 按钮 </button>
                </div>
        }
    }



  1. createRef

React.createRef()调用后可以返回一个容器,该容器可以存储被ref所标识的节点。但是该容器只能装下一个节点,如果需要获取多个节点就需要多次React.createRef()

    class Demo extends React.Component{
        myRef = React.createRef() //React.createRef()调用后可以返回一个容器
    	myRef2 = React.createRef() //该容器只能装下一个节点
    
        handleClick = () =>{
            console.log(this.myRef.current)
            console.log(this.myRef2.current)
        }
        render(){
            return 
            	<div>
                	<inpute ref={ this.myRef } type="text" placeholder="点击按钮提示数据" />
                   	<inpute ref={ this.myRef2 } type="text" placeholder="点击按钮提示数据" />
                    <button onClick={ this.handleClick }> 按钮 </button>
                </div>
        }
    }

受控组件 & 非受控组件

受到state状态管理的组件叫做受控组件

高阶函数

满足以下两个规范中的任何一个,都叫高阶函数

  1. 若A函数,接收的参数是一个函数,那个A就是高阶函数
  2. 若A函数,调用的返回值是一个函数,那个A就是高阶函数
    常见告诫函数: PromisesetTimeoutarr.map()等等

react事件处理原理

其实react并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡document处的时候----为了高效,React将事件内容封装并交由真正的处理函数运行----为了更好的兼容 。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

另外冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent) .因此我们如果不想要是事件冒泡的话调用event.stopProppagation()方法是无效的。而应该调用event.preventDefault().
react事件处理原理图

生命周期

旧生命周期:
在这里插入图片描述

挂载时:constructor—>componentWillMount—>render—>componentDidMount—>componentWillUnmount
更新时:

  1. 父组件render—>componentWillReceiveProps—>shouldComponentUpdate—>componentWillUpdate—>render—>componentDidUpdate—>componentWillUnmount
  2. this.setState()—>shouldComponentUpdate—>componentWillUpdate—>render—>componentDidUpdate—>componentWillUnmount
  3. 强制更新this.forceUpdate()—>componentWillUpdate—>render—>componentDidUpdate—>componentWillUnmount
    componentWillReceiveProps:第一次传的props不触发执行,一次之后的props传参才会执行
    shouldComponentUpdate:默认返回值是true,true才会往下更新,如果写了这个函数,则一定要返回一个布尔值

新生命周期函数:
在这里插入图片描述
挂载时:constructor—>getDerivedStateFormProps—>render—>componentDidMounted
更新时:getDerivedStateFormProps—>shouldComponentUpdate—>render—>getSnapshotBeforeUpdate—>componentDidUpdate

getDerivedStateFormProps:从props中接收派生状态,需要返回一个对象或者null,如果返回一个对象,则以后的state都是这个返回值,不能再修改。派生状态会导致带码冗余
getSnapshotBeforeUpdate:更新前给didupdate返回快照值
componentDidUpdate:接收3个参数,(prevprops,prevstate,snapshot)(旧值)

脚手架

全局安装react脚手架:yarn add create-react-app -g
创建项目:npx create-react-app demo_name

render prop(vue插槽)

class Demo extends React.Component{
	render(){
		return {
			<h1>Demo组件</h1>
			//🚩给A组件传递一个render函数,该函数接收A组件调用时传递过来的参数,并把参数传给了B组件
			//🚩A B组件是父子组件
			//在这里安置需要展示在A组件内部的组件
			<A render={(name) => <B name={name} />} />
		}
	}
}
class A extends React.Component{
	state = {name:'eee'}
	render(){
		const {name} = this.state
		return {
			<h1>A组件</h1>
			//A组件内通过props接收render函数并调用传参(state)
			//在A组件的这个地方刨坑,等待需要被展示的组件
			{this.props.render(name)}
		}
	}
}
class B extends React.Component{
	render(){
		return {
			//B组件接受A传递的name属性
			<h1>B组件name:{this.props.name}</h1>
		}
	}
}

props优先级:children > component > render
在这里插入图片描述

消息订阅 PubSub.js

安装:yarn add pubsub-js
引入:import PubSub from 'pubsub-js'
适用:非父子组件间传值
使用:

//componentDidMount钩子中订阅
 var token = PubSub.subscribe('MYTOPIC','mySubscribe') //🚩订阅
 /*
 	token:类似于定时器,方便以后清除
 	MYTOPIC:订阅消息名
 	mySubscribe:这是一个函数,接收两个参数,1.订阅消息名,2.形参
 */

PubSub.publish('MYTOPIC','helloWorld') //🚩发布

//componentWillUnmount钩子中取消订阅
PubSub.unsubscribe(token) //🚩清除订阅

redux(状态管理)

使用顺序

  1. 安装 yarn add redux
  2. 创建redux文件夹 store.js , reducer.js文件
  3. store.js
// 引入createStore,专门用于创建redux中最核心的store对象
import { createStore } from 'redux'
import reducer from './reducer'
let store = createStore(reducer)
export default store
  1. reducer.js
/* 
    该文件用于创建一个为Calc组件服务的reducer,reducer的本质是一个函数
    reducer函数会接到两个参数,分别为之前的状态prevState,动作对象action
*/
function count_reducer(prevState, action) {
    let newState = prevState
    return newState
}
export default count_reducer

context (子孙组件通信)

方法一:只适用于类式组件
关键词: createContext, Provider, static contextType

 //🚩为当前的 A 创建一个 context(“light”为默认值)
const MyContext = React.createContext('light');

class A extends React.Component{
	state = {name:'bb',age:15}
	render(){
		return {
			<h1>知识A组件,父</h1>
			//🚩将需要接受参数的子孙组件用Provider包裹住
			//🚩 传递的参数必须是value
			<MyContext.Provider value="this.state">
				<B />
			</MyContext.Provider>
		}
	}
}
class B extends React.Component{
	render(){
		return {
			<h1>知识B组件,子</h1>
			<C />
		}
	}
}
class C extends React.Component{
//🚩必须声明,才能接收到传下来的值,B组件没有声明,则B组件打印this.context为空
//contextType context类型,是MyContext类型
//如果不在一个文件里,在使用前还是需要引入MyContext的
//🅰 static contextType = MyContext;//第一种  定义静态变量  的方式
	render(){
		return {
			<h1>知识C组件,孙</h1>
			<span>能接收到A组件的值,{this.context.name},{this.context.age}</span>
		}
	}
}
//🅱 C.contextType = MyContext;//第二种  定义静态变量  的方式
export default C

方法二:适用于类式组件 & 函数组件
关键词: createContext, Provider,Consumer ,static contextType

const MyContext = React.createContext({thems:'light'});

class A extends React.Component{
	state = {name:'bb',age:15}
	render(){
		return {
			<h1>知识A组件,父</h1>
			<MyContext.Provider value="this.state">
				<B />
			</MyContext.Provider>
		}
	}
}
class B extends React.Component{
	render(){
		return {
			<h1>知识B组件,子</h1>
			<C />
		}
	}
}
function C (){
	return (
		<h1>知识C组件,孙</h1>
		//🚩 与方法一的区别之处
		//🚩 Consumer不需要定义contextType
		//🚩 Consumer接收一个函数,形参为接受的值
		<MyContext.Consumer>
		{
			value => `能接收到A组件的值,${value.name},${value.age}`
		}
		</MyContext.Consumer>
	)
}

关键词: createContext, Provider,Consumer ,static contextType,useContext

//在代码中,除了使用static contextType = MyContext的方法,还有C.contextType = MyContext,除此之外还有一个方法
import React,{useContext} from 'react'
const {thems} = useContext(MyContext)

在应用开发中一般不用context,一般都用它的封装react插件

组件通信法总结

父子组件: props
兄弟组件:消息订阅-发布,集中式管理
祖孙组件:消息订阅-发布,集中式管理,context(开发用的少,封装插件用的多)

PureComponent

当父组件调用setState后,react会重新调用render函数,这样的话,子组件会重新被调用,子组件的render函数也会被重新调用。但是有时候,父组件并没有传值给子组件,或则父组件传给子组件的props并没有变化。如此还要调用子组件的render会降低性能。所以我们可以通过组件的shouldComponentUpdate钩子函数来动态控制组件更新。但是如果传的props里有很多对象的话,对比起来会很麻烦。所以我们可以使用react的PureComponent来控制组件是否需要更新。PureComponent会根据传递的props值来动态的控制子组件是否需要更新

//使用方法
class Demo extends React.PureComponent{
render(){
		return {}
	}
}

hooks(使函数组件有自己的状态)

可以让你在不编写class的情况下使用state以及其他的react特性

State Hook

import {useState} from 'react'

function Example(){
  	// 声明一个叫 “count” 的 state 变量,并赋值为0
	const [count, setCount] = useState(0)
	
	return (
		<div>
	      <p>You clicked {count} times</p>
	      <button onClick={() => setCount(count + 1)}>
	        Click me
	      </button>
	    </div>
	)
}

声明多个state变量

function ExampleWithManyStates() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

Effect Hook

它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API

import React, { useState, useEffect } from 'react';

function Example(props) {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${props.count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候

为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

Hook使用规则

  • 只能在函数最外层调用hook,不要在循环,条件判断或者子函数中调用
  • 只能在react的函数组件中调用hook,不要在其他js函数中调用
    自定义hook

错误边界钩子函数

特点:只能捕获后代组件生命周期产生的错误,不能捕获自己产生的错误和其他组件在合成事件,定时器中产生的错误
∴这两个钩子是写在父组件里的,用来监控子组件的钩子函数
这两个钩子函数只有在生产环境的时候才有效,如果是在开发环境,则,奏效后还会报错

getDerivedStateFromError:

import Child from '../Child'
//生命周期函数,一旦后台组件报错,就会触发
state = {hasError:false}
static getDerivedStateFromError(){
	return {hasError:true}
}
return(){
	return {
		{this.state.hasError ? <span>组件渲染有误,请联系开发人员检查</span> : <Child / >}
	}
}

componentDidCatch

componentDidCatch(error,info){
	//统计羊肉面的错误,发送请求给后台
}

lazyLoad(懒加载)

import {Lazy,Suspense} from 'react'
//路由懒加载
const Home = Lazy(()=>import(./Home) )//只有等到导航被点击的时候才会加载Home组件
<Sunpense fallback={()=><span>加载中,请稍后...</span>}>//这个组件是为了,在还没加载完home前要展示的组件
	<Route path="/home" component={Home}></Route>
</Sunpense>

react数组注意点

注意点:

  1. react自己会对数据组做浅对比,如果值不变的话,页面不更新

这里的值,指变量,对象数组的栈地址,也就是说,如果一个对象的地址不变,reactr认为对象没变,不更新页面

typora

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值