React尚硅谷张天禹笔记(更新中......)

简介

英文官网: https://reactjs.org/

中文官网: https://react.docschina.org/

介绍描述

用于动态构建用户界面的 JavaScript 库(只关注于视图)

由Facebook开源

React的特点

声明式编码

组件化编码

React Native 编写原生应用

高效(优秀的Diffing算法)

React高效的原因

使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。

DOM Diffing算法, 最小化页面重绘。

	
	
  1. 本质是Object类型的对象(一般对象)
  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
  3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

组件名必须首字母大写

虚拟DOM元素只能有一个根元素

虚拟DOM元素必须有结束标签

JSX

1.定义虚拟DOM时,不要写引号。

2.标签中混入JS表达式时要用{}。

3.样式的类名指定不要用class,要用className。

4.内联样式,要用style={{key:value}}的形式去写。

5.只有一个根标签

6.标签必须闭合

7.标签首字母

(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。

(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

定义组件

函数式组件

//1.创建函数式组件
function MyComponent(){
	console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
	return 

我是用函数定义的组件(适用于【简单组件】的定义)

} //2.渲染组件到页面 ReactDOM.render(,document.getElementById('test'))

执行了ReactDOM.render(…之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

类式组件

//1.创建类式组件
class MyComponent extends React.Component {
	render(){
        //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
        //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
        console.log('render中的this:',this);
        return 

我是用类定义的组件(适用于【复杂组件】的定义)

} } //2.渲染组件到页面 ReactDOM.render(,document.getElementById('test'))

执行了ReactDOM.render(…之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。

3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

组件实例三大属性

state

原始

//1.创建组件
class Weather extends React.Component {

    //构造器调用几次? ———— 1次
    constructor(props) {
        console.log('constructor');
        super(props)
        //初始化状态
        this.state = { isHot: false, wind: '微风' }
        //解决changeWeather中this指向问题
        this.changeWeather = this.changeWeather.bind(this)	//将绑定在实例原型对象上的方法绑定在实例上
    }

    //render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
    render() {
        console.log('render');
        //读取状态
        const { isHot, wind } = this.state
        return 

今天天气很{isHot ? '炎热' : '凉爽'},{wind}

} //changeWeather调用几次? ———— 点几次调几次 changeWeather() { //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用 //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用 //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined console.log('changeWeather'); //获取原来的isHot值 const isHot = this.state.isHot //严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。 this.setState({ isHot: !isHot }) console.log(this); //严重注意:状态(state)不可直接更改,下面这行就是直接更改!!! //this.state.isHot = !isHot //这是错误的写法 } }

状态必须通过setState进行更新,且更新是一种合并,不是替换。

简写

//1.创建组件
class Weather extends React.Component {
    //初始化状态
    state = { isHot: false, wind: '微风' }

    render() {
        const { isHot, wind } = this.state
        return 

今天天气很{isHot ? '炎热' : '凉爽'},{wind}

} //自定义方法————要用赋值语句的形式 + 箭头函数 changeWeather = () => { const isHot = this.state.isHot this.setState({ isHot: !isHot }) } }
  1. 箭头函数的this指向定义时所在的外层第一个普通函数,跟使用位置没有关系
  2. 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变

props

ReactDOM.render(<Person name="jerry" age={19} sex="男" />, document.getElementById('test1'))
const p = { name: '老刘', age: 18, sex: '女' }
ReactDOM.render(, document.getElementById('test3'))

对象的解构只有在这里才能用,此处的{}不是指对象的浅拷贝

Person.propTypes = {
    name:PropTypes.string.isRequired, //限制name必传,且为字符串
    sex:PropTypes.string,//限制sex为字符串
    age:PropTypes.number,//限制age为数值
    speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
    sex:'男',//sex默认值为男
    age:18 //age默认值为18
}

必须先指定接收的属性类型再指定是否必须

React v15.5 开始已弃用在React上携带propTypes这样React太重了

现在需引入prop-types,用于对组件标签属性进行限制

constructor(props){
    //构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
    // console.log(props);
    super(props)
    console.log('constructor',this.props);
}

简写

static propTypes = {...}
static defaultProps = {...}
函数
//创建组件
function Person (props){
    const {name,age,sex} = props
    return (
        
  • 姓名:{name}
  • 性别:{sex}
  • 年龄:{age}
) } Person.propTypes = { name:PropTypes.string.isRequired, //限制name必传,且为字符串 sex:PropTypes.string,//限制sex为字符串 age:PropTypes.number,//限制age为数值 } //指定默认标签属性值 Person.defaultProps = { sex:'男',//sex默认值为男 age:18 //age默认值为18 }

refs

字符串
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
回调函数
<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>

更新时会传入两次,第一次传入null

createRef
myRef = React.createRef()
...
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
...
this.myRef.current.value

事件处理

(1).通过onXxx属性指定事件处理函数(注意大小写)

a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性

b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效

(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref

收集表单数据

非受控组件

现用现取

受控组件

相当于vue中的双向数据绑定

//初始化状态
state = {
    username:'', //用户名
    password:'' //密码
}

//保存用户名到状态中
saveUsername = (event)=>{
    this.setState({username:event.target.value})
}

高阶函数

高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。

2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:Promise、setTimeout、arr.map()等等

函数柯里化

//通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。 
function sum(a){
    return(b)=>{
        return (c)=>{
            return a+b+c
        }
    }
}

生命周期函数

//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
//强制更新
this.forceUpdate()

OLD

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaxV5j0b-1654776804145)(D:\黑马程序员$React\react全家桶资料\02_原理图\react生命周期(旧)].png)

初始化阶段: 由ReactDOM.render()触发 初次渲染

constructor()

componentWillMount()

render()

componentDidMount()

一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

更新阶段: 由组件内部this.setSate()或父组件render触发

shouldComponentUpdate()

componentWillUpdate()

render() =====> 必须使用的一个

componentDidUpdate()

卸载组件: 由ReactDOM.unmountComponentAtNode()触发

componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

挂载时:先执行构造器(constructor)=》组件将要挂载(componentWillMount)=》组件挂载渲染(render)=》组件挂载完成(componentDidMount)=》组件销毁(componentWillUnmount)

组件内部状态更新:组件是否应该更新(shouldComponentUpdate)=》组件将要更新(componentWillUpdate)=》组件更新渲染(render)=》组件更新完成(componentDidUpdate)

强制更新:调用this.forceUpdate(),这个api和setState一样都是react自带的,一般这个强制更新很少用,它的执行流程就是比上述的正常更新流程少一步询问是否更新(shouldComponentUpdate)

父组件重新render:调用组件将要接收新props(componentWillReceiveProps)=》组件是否应该更新(shouldComponentUpdate)=》组件将要更新(componentWillUpdate)=》组件更新渲染(render)=》组件更新完成(componentDidUpdate)

注意:上述加粗的函数,只有在父组件状态发生改变了,重新调用render时才会调用子组件的componentWillReceiveProps函数,父组件第一次引用子组件的时时不会调用的

NEW

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6e4vKIf-1654776804150)(D:\黑马程序员$React\react全家桶资料\02_原理图\react生命周期(新)].png)

新版生命周期函数和旧版的差别:

新版即将废弃老的3个钩子(componentWillMount、componentWillReceiveProps、componentWillUpdate)

新增了2个钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate)

//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
    console.log('getDerivedStateFromProps',props,state);
    return null
}

getSnapshotBeforeUpdate(){
    return this.refs.list.scrollHeight
}

componentDidUpdate(preProps,preState,height){
    this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}

DOM的Diff算法

虚拟DOM中的key的作用:

当状态中的数据发生改变时,react会根据【新数据】生成【新虚拟DOM】,随后react会进行【新虚拟DOM】和【旧虚拟DOM】的diff算法比较,具体的比较规则如下:

若【旧DOM】中找到了与【新DOM】相同的key,则会进一步判断两者的内容是否相同,如果也一样,则直接使用之前的真实DOM,如果内容不一样,则会生成新的真实DOM,替换掉原先的真实DOM
若【旧DOM】中没找到与【新DOM】相同的key,则直接生成新的真实DOM,然后渲染到页面
用index作为key可能引发的问题

若对数据进行:逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实DOM更新,造成效率低下
如果结构中还包含输入类的dom,会产生错误dom更新,出现界面异常
开发中如何选择key

最好选中标签的唯一标识id、手机号等
如果只是简单的展示数据,用index也是可以的

React脚手架

使用create-react-app(脚手架工具)创建一个初始化项目

1、下载脚手架工具:npm i -g create-react-app

2、创建引用:create-react-app my-app

3、运行应用:cd my-app(进入应用文件夹),npm start(启动应用)

public ---- 静态资源文件夹

​ favicon.icon ------ 网站页签图标

index.html -------- 主页面

​ logo192.png ------- logo图

​ logo512.png ------- logo图

​ manifest.json ----- 应用加壳的配置文件

​ robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

​ App.css -------- App组件的样式

App.js --------- App组件

​ App.test.js ---- 用于给App做测试

​ index.css ------ 样式

index.js ------- 入口文件

​ logo.svg ------- logo图

​ reportWebVitals.js

​ — 页面性能分析文件(需要web-vitals库的支持)

​ setupTests.js

​ ---- 组件单元测试的文件(需要jest-dom库的支持)

React脚手架配置代理

简单

在package.json中追加如下配置

“proxy”:“http://localhost:5000”

1、优点:配置简单,前端请求资源可以不加任何前缀
2、缺点:不能配置多个代理(如果请求的不同服务器就不行)
3、工作方式:当请求了自身3000端口不存在的资源时,那么会转发给5000端口(优先会匹配自身的资源,如果自己有就不会请求5000端口了)

麻烦

创建代理配置文件

在src下创建配置文件:src/setupProxy.js

编写代理配置规则

const {createProxyMiddleware:proxy} = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
      	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}

1、优点:可以配置多个代理,可以灵活控制请求是否走代理
2、缺点:配置繁琐,前端请求资源时必须加前缀

消息订阅-发布机制

工具库:PubSubJs

下载:npm install pubsub-js --save

先引入:import PubSub from “pubsub-js”

要接收数据方订阅 PubSub.subscribe(‘消息名’,(data)=>{ console.log(data) })
传递数据方发布 PubSub.publish(‘消息名’,data)

路由

一个路由就是一个映射关系
key永远为路径,value可能是function或者component

路由分类

后端路由

后端路由的key还是路径,只不过value是上述说的function

注册路由:router.get(path, function(req,res){…})
工作过程:当node接收到一个请求时,会根据请求路径去匹配对应的路由,然后调用对应路由中的函数来处理请求,返回响应数据

前端路由

浏览器端路由,value是对应组件(component),用于展示页面内容
注册路由:
工作过程:当浏览器path变为/test时,当前路由组件就会变成Test组件

react-router-dom

内置组件
路由的基本使用
//导航区的a标签改为Link标签
Demo
//展示区写Route标签进行路径的匹配

//的最外侧包裹了一个
路由组件与一般组件
写法不同

一般组件:

路由组件:

存放位置不同:

一般组件:components
路由组件:pages

接收到的props不同:

一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性

history:
    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)
location:
    pathname: "/about"
    search: ""
    state: undefined
match:
    params: {}
    path: "/about"
    url: "/about"
NavLink与封装NavLink

NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

Switch的使用
  • 通常情况下,path和component是一一对应的关系。
  • Switch可以提高路由匹配效率(单一匹配)。
  • 匹配一个成功后,后续相同路径组件也不会生效了
解决多级路径刷新页面样式丢失的问题
  • public/index.html 中 引入样式时不写 ./ 写 / (常用)
  • public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
  • 使用HashRouter
路由的严格匹配与模糊匹配
  • 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

  • 开启严格匹配:

  • 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用

一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由


    
    
    

嵌套路由
  • 注册子路由时要写上父路由的 path 值
  • 路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
params参数
路由链接(携带参数) 详情
注册路由(声明接收)
接收参数 this.props.match.params
search参数
路由链接(携带参数) 详情
注册路由(无需声明,正常注册即可)
接收参数 this.props.location.search
备注 获取到的search是urlencoded编码字符串,需要借助querystring解析
state参数
路由链接(携带参数) 详情
注册路由(无需声明,正常注册即可)
接收参数 this.props.location.state
备注 刷新也可以保留住参数
编程式路由导航

借助this.props.history对象上的API对操作路由跳转、前进、后退

this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
BrowserRouter与HashRouter的区别
底层原理不一样

BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

HashRouter使用的是URL的哈希值。

path表现形式不一样

BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

HashRouter的路径包含#,例如:localhost:3000/#/demo/test

刷新后对路由state参数的影响

(1).BrowserRouter没有任何影响,因为state保存在history对象中。

(2).HashRouter刷新后会导致路由state参数的丢失!!!

push与replace

默认开启的是push模式,push模式就是说每次的点击跳转改变路径,都是往浏览器历史记录的栈中不断追加一条记录,然后你点回退按钮时,它会指向当前栈顶记录的前一条,replcae模式就是说替换掉当前的那条记录,然后你点回退的时候,就不会显示上次被替换掉的那条记录了,只会显示上上条记录,那要怎么设置为replace模式呢?直接在****标签上添加一个replace属性即可

withRouter

作用:它就是专门解决在一般组件中想要使用路由组件的那几个API的这个问题的,它接收一个一般组件,然后调用后,该一般组件身上也有了路由组件的history、match等属性

import {withRouter} from 'react-router-dom'

class Header extends Component {}
export default withRouter(Header)

//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件

UI组件库

material-ui(国外)

官网: http://www.material-ui.com/#/

Github: https://github.com/callemall/material-ui

ant-design(国内蚂蚁金服)

官网: https://ant.design/index-cn

Github: https://github.com/ant-design/ant-design/

redux

简介

  • 它是专门做状态管理的js库,不是react插件库

  • 它可以用在angular、vue、react等项目中,但与react配合用到最多

  • 集中式管理react应用中多个组件共享的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLlNdpoR-1654776804155)(D:\黑马程序员$React\react全家桶资料\02_原理图\redux原理图.png)]

基本使用

  1. 去除Count组件自身的状态

  2. src下建立:

    redux/

    ​ store.js

    ​ count_reducer.js

  3. store.js:

//引入redux中的createStore函数,创建一个store
import {createStore} from 'redux'
//createStore调用时要传入一个为其服务的reducer
import countReducer from './count_reducer'
//暴露store对象
export default createStore(countReducer)
  1. count_reducer.js:
/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数,返回加工后的状态
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
	3.reducer有两个作用:初始化状态,加工状态,reducer被第一次调用时,是store自动触发的
*/
import {
    INCREMENT,
    DECREMENT
} from "./constant";

const initState = 0
export default function countReducer(previousState = initState, action) {
    console.log(previousState, action); //undefined {type: '@@redux/INIT1.m.x.g.h.b'}
    const {
        type,
        data
    } = action
    switch (type) {
        case INCREMENT:
            return previousState + data
        case DECREMENT:
            return previousState - data
        default:
            return previousState
    }
}
  1. 在index.js中监测store中状态的改变,一旦发生改变重新渲染****
store.dispatch({type:'increment',data:value*1})
ReactDOM.render( < App / > , document.getElementById('root'))
store.subscribe(() => {
	ReactDOM.render( < App / > , document.getElementById('root'))
})
  1. 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

action

//count_action.js 专门用于创建action对象
export const increase = data => ({type: INCREMENT, data})

异步action

//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))


//count_reducer.js
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
        //异步任务有结果后,分发一个同步的action去真正操作数据
		setTimeout(()=>{
            //异步action中一般都会调用同步action
			dispatch(createIncrementAction(data))
		},time)
	}
}

react-redux

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEB5n4W4-1654776804156)(D:\黑马程序员$React\react全家桶资料\02_原理图\react-redux模型图.png)]

react-redux基本使用

//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 
	1.mapStateToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
	return {count:state}
}

/* 
	1.mapDispatchToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
	return {
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

react-redux优化

  1. 容器组件和UI组件整合一个文件
  2. 无需自己给容器组件传递store,给包裹一个****即可
  3. 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作
  4. mapDispatchToProps也可以简单的写成一个对象
{
    jia:createIncrementAction,
        jian:createDecrementAction,
            jiaAsync:createIncrementAsyncAction,
}
  1. 一个组件要和redux“打交道”要经过哪几步?

    定义好UI组件—不暴露
    引入connect生成一个容器组件,并暴露,写法如下:

    connect(
    	state => ({key:value}), //映射状态
    	{key:xxxxxAction} //映射操作状态的方法
    )(UI组件)
    

    在UI组件中通过this.props.xxxxxxx读取和操作状态

react-redux数据共享版

为Person组件编写:reducer、action,配置constant常量。

重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
	he:countReducer,
	rens:personReducer
})

交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

react-redux开发者工具的使用

import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

react-redux最终版

  • 所有变量名字要规范,尽量触发对象的简写形式。
  • reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

项目打包运行

npm run build 它会生成一个build文件夹

服务器搭建:

  • 用node+express可以搭建一个简单的服务器
  • 需要用到一个库serve,使用前需要先下载npm i serve -g,然后进入build文件夹中执行serve即可

拓展

setState

对象式

setState(stateChange, [callback])------对象式的setState

  1. stateChange为状态改变对象(该对象可以体现出状态的更改)
  2. callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
//异步调用
this.setState({count:count+1},()=>{
    //更新后的值
	console.log(this.state.count);
})
//更新前的值
console.log(this.state.count);
函数式

setState(updater, [callback])------函数式的setState

  1. updater为返回stateChange对象的函数。
  2. updater可以接收到stateprops
  3. callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
this.setState( state => ({count:state.count+1}))

使用原则:

  • 如果新状态不依赖于原状态 ===> 使用对象方式
  • 如果新状态依赖于原状态 ===> 使用函数方式
  • 如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
import Loading from './Loading'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))

//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
}>
        
            
            
        

Hooks

React Hook/Hooks是什么
  • Hook是React 16.8.0版本增加的新特性/新语法
  • 可以让你在函数组件中使用 state 以及其他的 React 特性
State Hook

让函数组件也可以有state状态, 并进行状态数据的读写操作

const [xxx, setXxx] = React.useState(initValue)
useState()说明
参数 第一次初始化指定的值在内部作缓存
返回值 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数

setXxx()2种写法:

  • setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
  • setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
Effect Hook

用于模拟类组件中的生命周期钩子

React.useEffect(() => { 
	// 在此可以执行任何带副作用操作
		return () => { // 在组件卸载前执行
		// 在此做一些收尾工作, 比如清除定时器/取消订阅等
	}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

可以把 useEffect Hook 看做如下三个函数的组合

  • componentDidMount()
  • componentDidUpdate()
  • componentWillUnmount()
Ref Hook

可以在函数组件中存储/查找组件内的标签或任意其它数据

const myRef = React.useRef()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPRbdsO3-1654776804157)(C:\Users\邱嘎噶\AppData\Roaming\Typora\typora-user-images\image-20220605171554829.png)]

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

export default function Son(props) {
    // 使用refs
    const qggRef = React.useRef()
    const showRef = () => {
        alert(qggRef.current.innerHTML)
    }
    // 使用state
    const [qggState, setQggState] = React.useState('qggState')
    const changeQggState = () => {
        setQggState(value => value + 'nb')
    }
    const death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }
    // 使用Effect模仿三个生命周期钩子
    React.useEffect(() => {
        const timer = setInterval(
            () => {
                setQggState(value => value + 'nb')
            }, 1000)
        return () => {
            clearInterval(timer)
        }
    }, [])
    return (
        
Son
  • state:{qggState}
  • props:{props.qggProps}


) }

Fragment

这个标签就是用在有时页面结构层级太多,而且有些都是语法要求,实际没意义的结构层级(return()中的根节点就是这个情况),这时你就可以用Fragment标签,当然<>在一般情况下和Fragment标签作用相同,当时有一点不一样,就是Fragment标签能接收一个key属性,而<>什么属性都不能接收

import React, { Component, Fragment } from 'react'

	
	

<>

Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

  1. 创建Context容器对象:

    const XxxContext = React.createContext()  
    
  2. 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:

    
    	子组件
    
    
  3. 后代组件读取数据:

    //第一种方式:仅适用于类组件 
      static contextType = xxxContext  // 声明接收context
      this.context // 读取context中的value数据
    
    //第二种方式: 函数组件与类组件都可以
      
        {
          value => ( // value就是context中的value数据
            要显示的内容
          )
        }
      
    

组件优化

Component的2个问题
  1. 只要执行setState(),即使不改变状态数据,组件也会重新render() ==> 效率低
  2. 只要当前组件重新render(),就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
原因

Component中的shouldComponentUpdate()总是返回true

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

解决
重写shouldComponentUpdate()方法

比较新旧state或props数据,如果有变化才返回true,如果没有返回false

缺点:得一个一个写

父组件
shouldComponentUpdate(nextProps,nextState){
    // console.log(this.props,this.state); //目前的props和state
    // console.log(nextProps,nextState); //接下要变化的目标props,目标state
    return !this.state.carName === nextState.carName
}
子组件
shouldComponentUpdate(nextProps,nextState){
	console.log(this.props,this.state); //目前的props和state
	console.log(nextProps,nextState); //接下要变化的目标props,目标state
	return !this.props.carName === nextProps.carName
}
PureComponent

PureComponent重写了shouldComponentUpdate(),只是进行state和props数据的浅比较

只有state或props数据有变化才返回true,如果只是数据对象内部数据变了,返回false

render props

向组件内部动态传入带内容的结构(标签)

children props

通过组件标签体传入结构

//父组件

  xxxx

//子组件
{this.props.children}

问题: 如果B组件需要A组件内的数据 ==> 做不到

render props

通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

//祖组件
 }>
//A组件
{this.props.render(内部state数据)}
//C组件读取A组件传入的数据
{this.props.data} 

错误边界

Error boundary:用来捕获后代组件错误,渲染出备用页面

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:getDerivedStateFromError配合componentDidCatch

import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {

	state = {
		hasError: '' //用于标识子组件是否产生错误
	}

	// 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
    // 在render之前触发
    // 返回新的state
	static getDerivedStateFromError(error) {
		console.log(error);
		return { hasError: error }
	}

	componentDidCatch() {
		console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
	}

	render() {
		return (
			

我是Parent组件

{this.state.hasError ?

当前网络不稳定,稍后再试

: }
) } }

组件通信方式总结

props children props
render props
消息订阅-发布 pubs-sub、event
集中式管理 redux、dva
Context 生产者-消费者模式

搭配方式

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

你可能感兴趣的:(react.js,javascript,前端)