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

简介

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

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

介绍描述

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

由Facebook开源

React的特点

声明式编码

组件化编码

React Native 编写原生应用

高效(优秀的Diffing算法)

React高效的原因

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

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

	<!-- 准备好一个“容器” -->
	<div id="test"></div>

	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/react.development.js"></script>
	<!-- 引入react-dom,用于支持react操作DOM -->
	<script type="text/javascript" src="../js/react-dom.development.js"></script>
	<!-- 引入babel,用于将jsx转为js -->
	<script type="text/javascript" src="../js/babel.min.js"></script>

	<script type="text/babel"> /* 此处一定要写babel */
		//1.创建虚拟DOM
        
		//使用JS创建
        // const VDOM = React.createElement('h1', {id:'qgg'},React.createElement('span', {},'Hello  World'))
        
        //使用JSX创建
        const VDOM = (/* 此处一定不要写引号,因为不是字符串 */
        <h1 id='qgg'>
            <span>Hello World</span>
        </h1>)
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM, document.getElementById('test'))
	</script>
  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 <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,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 <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
    }
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,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 <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
    }

    //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 <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
    }

    //自定义方法————要用赋值语句的形式 + 箭头函数
    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(<Person {...p} />, 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 (
        <ul>
        	<li>姓名:{name}</li>
			<li>性别:{sex}</li>
			<li>年龄:{age}</li>
		</ul>
)
}
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标签
<Link to="/xxxxx">Demo</Link>
//展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
//<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
路由组件与一般组件
写法不同

一般组件:

路由组件:

存放位置不同:

一般组件: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指定的路由

<Switch>
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    <Redirect to="/about"/>
</Switch>
嵌套路由
  • 注册子路由时要写上父路由的 path 值
  • 路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
params参数
路由链接(携带参数)<Link to=‘/demo/test/tom/18’}>详情</Link>
注册路由(声明接收)<Route path=“/demo/test/:name/:age” component={Test}/>
接收参数this.props.match.params
search参数
路由链接(携带参数)<Link to=‘/demo/test?name=tom&age=18’}>详情</Link>
注册路由(无需声明,正常注册即可)<Route path=“/demo/test” component={Test}/>
接收参数this.props.location.search
备注获取到的search是urlencoded编码字符串,需要借助querystring解析
state参数
路由链接(携带参数)<Link to={{pathname:‘/demo/test’,state:{name:‘tom’,age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可)<Route path=“/demo/test” component={Test}/>
接收参数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模式呢?直接在**<Link replace to=‘XXX’>**标签上添加一个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中状态的改变,一旦发生改变重新渲染**<App/>**
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,给<App/>包裹一个**<Provider store={store}></Provider>**即可
  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.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<Loading />}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
</Suspense>

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 (
        <div className="son">
            <span>Son</span>
            <ul>
                <li ref={qggRef}>state:{qggState}</li>
                <li>props:{props.qggProps}</li>
                <li><button onClick={showRef}>refs</button></li>
            </ul>
            <button onClick={changeQggState}>setQggState</button><br />
            <button onClick={death}>death</button><br />
        </div>
    )
}

Fragment

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

<Fragment><Fragment>
import React, { Component, Fragment } from 'react'
<Fragment key={1}>
	<input type="text"/>
	<input type="text"/>
</Fragment>
<></>

Context

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

  1. 创建Context容器对象:

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

    <xxxContext.Provider value={数据}>
    	子组件
    </xxxContext.Provider>
    
  3. 后代组件读取数据:

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

组件优化

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

通过组件标签体传入结构

//父组件
<A>
  <B>xxxx</B>
</A>
//子组件
{this.props.children}

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

render props

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

//祖组件
<A render = {data => <C data={data} />}></A>
//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 (
			<div>
				<h2>我是Parent组件</h2>
				{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
			</div>
		)
	}
}

组件通信方式总结

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

搭配方式

父子组件props
兄弟组件消息订阅-发布、集中式管理
祖孙组件(跨级组件)消息订阅-发布、集中式管理、Context(开发用的少,封装插件用的多)
  • 3
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值