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,随后呈现到页面中
*/
类式组件
类的相关复习
- 类的构造器不是必须写的,要对实例进行一些初始化操作,如添加指定属性时才写
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须写的
- 类中所定义的方法,都是放在了类的原型对象上,供实例对象使用
类式组件
//创建类式组件
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
-
字符串形式的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>
}
}
-
回调函数形式的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>
}
}
-
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>
}
}
-
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状态管理的组件叫做
受控组件
高阶函数
满足以下两个规范中的任何一个,都叫
高阶函数
- 若A函数,接收的参数是一个函数,那个A就是高阶函数
- 若A函数,调用的返回值是一个函数,那个A就是高阶函数
常见告诫函数:
Promise
、setTimeout
、arr.map()
等等
react事件处理原理
其实react并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且
冒泡
到document
处的时候----为了高效,React将事件内容封装并交由真正的处理函数运行----为了更好的兼容 。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
另外冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(
SyntheticEvent
) .因此我们如果不想要是事件冒泡的话调用event.stopProppagation()方法是无效的。而应该调用event.preventDefault()
.
生命周期
旧生命周期:
挂载时:
constructor—>componentWillMount—>render—>componentDidMount—>componentWillUnmount
更新时:
- 父组件render—>componentWillReceiveProps—>shouldComponentUpdate—>componentWillUpdate—>render—>componentDidUpdate—>componentWillUnmount
- this.setState()—>shouldComponentUpdate—>componentWillUpdate—>render—>componentDidUpdate—>componentWillUnmount
- 强制更新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(状态管理)
使用顺序
- 安装
yarn add redux
- 创建redux文件夹 store.js , reducer.js文件
- store.js
// 引入createStore,专门用于创建redux中最核心的store对象
import { createStore } from 'redux'
import reducer from './reducer'
let store = createStore(reducer)
export default store
- 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数组注意点
注意点:
- react自己会对数据组做浅对比,如果值不变的话,页面不更新
这里的值,指
变量,对象数组的栈地址
,也就是说,如果一个对象的地址不变,reactr认为对象没变,不更新页面
typora