react组件分类

函数式组件,并不是后面讲的Hooks

1.无状态组件 - React16.8之前,函数式组件不支持状态,支持属性

2.class类组件 - 支持状态和属性

3.react Hooks - React16.8之后,新增Hooks支持状态,支持属性

4.可控组件和非受控组件 - 表单元素

5.快捷方式

rcc快捷输入方式(类组件); rfc快捷方式(无状态组件)

Component

0.数据更新

render每次只进行一次渲染,应该将render封装进一个函数,在数据第一次加载和数据更新时多次调用;或者使用this.forceUpdate()强制更/刷新;再或者使用官方推荐的setState修改状态后数据进行重新更新(先删除后新增)

1.结构

根目录新建一个components文件夹专门放组件,再新建每个文件就是一个组件,这个组件包含他的js(或者jsx)和css。

即jsx 或js 都可以是react组件的扩展名

组件可以是.js也可以是.jsx,是由babel负责解析。.vue是由vue loader来解析。

组件的人return的是DOM元素必须有根节点,若不想要根节点:从Reatc引入Fragment标签作为空标记或者直接写<></>表示空标记

2.组件分类

1.无状态组件

多种写法:

const 组件名(首字母大写)=(props)=>{
return (jsx表达式)
}

  1. <script type="text/babel">
  2. function Box(props) {
  3. return (
  4. <div>欢迎学习{props.content}</div>
  5. )
  6. }
  7. const App = (props) => {
  8. return (
  9. <div>欢迎学习{props.content}</div>
  10. )
  11. }
  12. //ReactDOM.render(<App content="react组件"></App>, document.querySelector("#app"));
  13. ReactDOM.render(App({ content: "react组件" }), document.querySelector("#app"));
  14. </script>

2.拓展-react新特性hook
  1. useState - 在无状态组件里可以使用状态(优点:不用写constructor和钩子函数)
    用法: let [数据,修改数据的函数]=useState(数据的初始值);//解构写法,其实是一个数组

3.类组件

类组件的四个属性: state,props,ref,context

class 组件名(首字母大写) extends React.Component{
constructor(props){//构造函数(器)-一定要传props,不然就收不到
super(props);//借用了父类的构造函数初始化自己;
若super()不传props,则拿不到this.props,只能拿到构造函数传过来的props。等价于this.props=props。其实ES6的class继承也是必须用super,才能拿到this

this.xxx = props.xxx;//props每一项单独写出来,免得使用时频繁写props
this.state = {//自身数据,使用时通过this.state取值,区分与props
xx: “xx”;
}//可以利用生命周期函数调用数据更新
}
render(){
//在render中的this包含常用的属性有state、props、refs
//state组件内部的数据,props来自父组件的数据,refs标识一个组件的节点
return jsx表达式
}//render是必不可少的钩子函数

}

在脚手架中如果constructor只有props,则不用写,写了反而警告

this.props和this.state解构赋值时最好写默认值,这样不会报致命性错误 let {a=”默认值}=this.props;

4.受控组件与非受控组件(半受控组件)

“受控”与“非受控”两个概念,区别在于这个组件的状态是否可以被外部修改。一个
设计得当的组件应该同时支持“受控”与“非受控”两种形式,即当开发者不控制组件属性
时,组件自己管理状态,而当开发者控制组件属性时,组件该由属性控制。
https://www.yuque.com/ant-design/course/goozth

value 改为defaultValue, checked 改为 defaultChecked

  1. 普通组件来说,他的数据都来自于外部(props),这个组件则是受控组件
  2. 表单元素是一个特例(表单中的value不能独立出现)。他的值若来自于state,这个组件就是受控组件。注意事项:value需要和onChange同时出现(通过setState可以实现v-model的效果);如果只是静态的显示可以使用defaultValue (或者是defaultChecked->只读状态)

3.组件之间的交互
  1. 父传子: 通过属性传递,props接收
    props传给子组件的值不能由子组件私自修改

  2. 子传父: 回调函数

  1. 子组件调用父组件传递过来的方法来实现的。需传递的数据放到参数里去

  2. —-即子组件函数的返回值是父组件的函数名,通过函数名执行父组件的函数 或者 返回值是父组件函数的参数

this.props.parent事件(this.child事件)

  1. 直接给子组件绑定ref: this.refs.child子组件来获取子组件的状态和事件的执行。例如:this.refs.子组件.state.xxx 和 this.refs.子组件.事件()

  1. 子与子通信,通过父组件作为枢纽(中间人模式)

  2. 发布订阅模式

  3. 非父子通信使用Context来进行通信: react自带模块,且redux依赖于Context
    Context博客地址

  1. cnpm i pubsub-js -S
  2. 非父子组件传值: 订阅者的回调函数放在Pubsub,发布者执行订阅者的回调函数
  3. 发布/触发:PubSub.publish("事件名","数据");
  4. 订阅/监听:PubSub.subscribe("事件名",(msg,data)=>{
  5. //msg是事件名,data是传过来的参数
  6. })

4.手动切换组件(即vue动态组件)

<this.state.targetChild></this.state.targetChild>在state定义个变量,这个变量的值为想要显示的子组件的名字(当然待切换的子组件都要引入)

5.插槽slot-没有具名插槽的概念(但是是一个数组,可通过序号取到)

即子节点:(不传是undefined,一个子节点就是一个对象,多个就是对象为元素组成的数组); Array.isArray(this.props.children)表示是一个真数组。

父组件在子组件标签上通过属性传递的数据直接可以props中(其中属性名为key,属性值为value)获取。

而slot是放在标签的innerHTML中,传递不是简单数据而是一块页面结构里面可能包含了事件的调用。他也会出现在子组件中的props(处在this.props.children:[],每个最大的元素就是数组的某一项)。

6.进一步使用插件来对props做数据类型的规范
  1. 资料来源https://www.npmjs.com/package/prop-types 利用第三方prop-types来处理 让props看起来有数据类型约束(js是弱语言没有数据类型的概率)

  2. 安装: npm install —save prop-types

  3. 引入: import PropTypes from ‘prop-types’;

  4. 使用

    1. static propTypes={
    2. usename: PropTypes.string,
    3. //如类型不匹配:运行代码,结果不会错页面效果也有,但是控制台会报错
    4. }
    5. /*
    6. optionalArray: PropTypes.array,
    7. optiona1Boo1: PropTypes.boo1,
    8. optiona1Func: PropTypes.func,
    9. optiona1Number: PropTypes.number,
    10. optiona10bject: PropTypes.object,
    11. optiona1String: PropTypes.string,
    12. optiona1Symbo1: PropTypes.symbo1,
    13. */

7.props的默认值和限制类型

es3开始 es4(偏向java强语言类型-没有特点-废弃) es5(map,Object.definePrototype) es6-箭头 es7-static

  1. //1.默认值
  2. 类组件:static defaultProps={key键:默认值};
  3. 无状态组件:组件名.defaultProps={key键:默认值};//prototype
  4. //2.限制类型
  5. import PropTypes from "prop-types";// 用的是PropTypes内的方法来验证。其实也可以自己写方法来验证,但是它包含了方法,用就行了
  6. //不用安装,脚手架自带。文档:https://www.npmjs.com/package/prop-types
  7. {/* 类组件(两种方法都支持-一般写第一种); 无状态组件似乎只支持第二种 */}
  8. 方法1. (es7的写法static加静态属性):static propTypes={key键:PropTypes.类型(小写).isRequired(必传)};
  9. 方法2. (es6写法加静态属性):
  10. 组件名.propTypes={key键:PropTypes.类型(小写).isRequired(必传)};//prototype(静态属性或类属性-不用实例化)

8.异步数据的传值
  1. <p>异步数据的请求处理方法</p>
  2. 1. <p>swiper-轮播图:先请求数据,再在更新阶段钩子函数实例化swiper(或者在setState和axios的回调里实例化)</p>
  3. 优雅地利用函数的副作用(此处的副作用是干其他事)
  4. <Swiper />
  5. <hr />
  6. 2. 类似于v-if
  7. <p>swiper-轮播图(好处:解耦分离):onlyswiper只管swiper的实例和布局;数据由父组件请求到了之后通过props子组件(三目判断有数据后再渲染)</p>
  8. <Myswiper />
  9. <hr />

生命周期

react核心算法由fiber(diff比对仍然存在,只优化了渲染)替代diff。因为的渲染可以被打断,导致带will的钩子函数就不安全

fiber算法 (菜鸟勿入)

// 16.2 版本之前 - 老的生命周期

// 16.2 版本之后 - 废弃了3个老的钩子(componentWillMount挂载之前、componentWillUpdate更新之前(不要写setState,会陷入死循环)、componentWillReceiveProps子组件更新props时可拿到最新来自父组件的props),添加了2个新的钩子

静态方法拿不到this

挂载阶段的生命周期函数(执行顺序由上到下) 加粗的是重要常用的
constructor(props,过时的 Context) 初始化,props借用了父类的构造函数初始化自己,初始化state,将时间处理函数绑定到类实例上。只在挂载阶段执行一次(因此接收新的props更新state由下面这个钩子函数)
static getDerivedStateFromProps(nextprops,state){return{存于state的key:props.父传过来的值}} 静态方法,不能用this(可能实例化还没完成)
[新增]从props中获取派生状态Derived State
废弃: componentWillMount
前缀: UNSAFE_componentWillMount(版本17之后加了前缀才能使用)
在ssr中会出现bug,不够安全高效
render() 必需的,被调用时先计算propsstate,并return返回(React元素、字符串和数字以文本节点、null和bool不渲染)
componentDidMount() 组件被装配后立即调用。初始化使得DOM节点应该进行到这里。通常在这里进行ajax请求如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
卸载阶段的生命周期函数
componentWillUnmount() 1.从节点卸载ReactDOM.unmountComponentAtNode ( document.getElementById(“ root”))
2.路由卸载组件
由这个钩子函数在卸载组件时清理定时器等等,以免内存泄漏
更新阶段的生命周期函数 important
static getDerivedStateFromProps(nextprops,state)
替代componentWillMount和componentWillReceiveProps
{return {state的值: props.xxx}}
第一次初始化挂载和更新都会执行
从props中获取派生状态更新state,频繁传入只取return最后一次,解决多次请求bug
使用方法: 1.把父组件传过来的最新nextprops存于子组件的state,再在componentDidMount和component DidUpdate中对state操作(因为这个钩子静态方法不能做异步,只能配合component DidUpdate做异步) 2.或者只用最新的props值return null 3.或者修改/加工老状态state
1. shouldComponentUpdate(nextProps,nextState){return 布尔值}
2. 纯组件PureComponent替代Component,进行浅比对,实现性能的优化import React, {Component, PureComponent} from 'react'
==不是万精油:==内部一直进行对比(shallowEqual),但是如果倒计时这种你明明知道状态或属性一直处于变化,还用纯组件一直对比,反而效率更低。因此这种shouldComponentUpdate判断倒计时到0了不在更新反而效率更高
3. var Item=React.memo((props)=>{return (…)});
React.memo(组件)。只应用于无状态组件进行性能优化<参数是组件,返回值也是组件,这类组件我们称为'高阶组件HOC'>
本质是高阶函数(map,filter,forEach…)
vue会比较前后状态没有更新不会重新渲染,但是react不会自己比较,而是在这个生命周期通过判断老状态this.state/props和新状态两个参数对比判断是否重新渲染视图
询问组件是否更新若return true;就会触发render;若return false;不会触发render。
注意:只能判断props和state的最外层(浅比对,即最外层),比如props是对象,里面的改动检测不到。可以把前后状态转化为JSON字符串比较
性能差bug:若不设置这个钩子,每次都更新,不管状态改没改变都会重新渲染;而且只改了一项,但是render多次
setState:若没有这个钩子,只要执行setState,不管状态改没改变,都会触发本身与所有子组件的更新,拖性能
render()
getSnapshotBeforeUpdate(prevProps,PrevState)
在render之后执行。使用场景: 趁render刚刚执行的最后时间,获取最新的老/之前的this.node.scrollHeight,在于下个钩子作比较,得到每次增加的差值 — 新旧值比较
在更新之前获取快照
注意:必须和componentDidUpdate一起使用;必须return一个数,这个数作为它搭档的第三个参数snapshot;不能和旧款的钩子函数一起使用。
目的是为了返回数据更新前的dom状态
componentDidUpdate(prevProps,PrevState,snapshot) 可以理解为更新之后
可以单飞。更新之后,快照(更新前的数据)与现有数据的相互比较操作
单飞时检测组件里的数据变化(多用于异步数据的返回和路由参数的变化),且慎用setState,否则数据一更新又会触发这个钩子,陷入死循环。
==废弃:==componentWillUpdate
在render之前执行,不能在这里面写setState,会陷入死循环。被snapshot替代
使用概率低,且有隐患(若与component DidUpdate的事件过长,则DOM信息不可信)。例子:正在看一封邮件,突然来了新邮件,重新设置scrollTop,使当前观看邮件高度不便—解决snapshot
==废弃:==componentWillReceiveProps; 加前缀表示你接收不安全的隐患 只要执行setState,就会执行这个钩子<解决: 在父组件异步请求,只传递结果给子组件>
错误处理(2个)

1.constructor

继承自React.Component则super必须写,不管有没有props

setState写了才修改,不写不修改,修改后与原state混合-具有刷新视图的功能。不是object.defineproperty数据劫持(get,set拦截); 而是setState(当年对拦截没有尝试,而是自己设置更改状态的方式)

  1. export default class Index extends React.Component({
  2. {/* class类函数的一般写法 */}
  3. myname = '直接定义变量'
  4. state = {} // 直接这样定义的state- 还是用构造器吧
  5. say(){console.log('构造函数内定义函数')}
  6. say2=()=>{console.log('箭头')}
  7. {/* 构造器,抄袭的java,是一个语法糖 */}
  8. constructor(props) {
  9. super(props)// 继承自React.Component则必须写,不管有没有props
  10. this.state = {
  11. }
  12. console.log(props)
  13. }
  14. })

1583990055311

高阶组件

概念: 我们把 参数是组件 返回值也是组件 这类组件我们叫 “高阶组件”(HOC)
本质是高阶函数 (map filter forEach ….)

0.高阶组件withRouter

路由切换的组件才能拿到this.props中路由的3个属性。但是经过withRouter(一般最外层)返回的高阶组件可以是组件拥有路由的3个属性。

1.属性代理

高阶组件中返回的组件一定要传递props,不然被包裹的组件拿不到props,传递之后返回的组件才能进行父子属性传值。下面举例:

进入一个组件,验证是否登录,没登录,先记下当前的地址,在重定向到login,登陆之后跳转到记录的地址 - hoc或者权限路由(三目; if判断; &&符)

  1. import React from 'react'
  2. import { Redirect } from 'react-router-dom'
  3. export default function Hoc(BackComponent){
  4. // 其实props传递给我(return的class组件)了,我要继续往下面传递给返回的组件--即属性代理
  5. return class extends React.component{
  6. render(){
  7. sessionStorage.setItem('page',this.props.location.pathname)
  8. if(sessionStorage.getItem('user')){
  9. // 扩展运算符传递所有props,经过hoc返回的组件才能够进行父子属性传值
  10. return <BackComponent {...this.props} />
  11. }else{
  12. return <Redirect to="/login" />
  13. }
  14. }
  15. }
  16. }// 没登录则这个组件无法访问
  17. // 可省略高阶组件的名字Hoc直接导出; 也可省略return的类组件的名字Test(直接retur和导出)

2.反向继承

继承自参数的这个组件; 调用super.render()

  1. // 反向继承或渲染劫持 - 调用super.render()
  2. export default (BackComponent, integral = 10) => {
  3. // 1.不在继承自React.Component,而是继承自返回的组件
  4. return class extends BackComponent {
  5. render () {
  6. return <div>
  7. { // super.render()调用继承的返回组件的render,即DOM内容 ===等价于渲染<BackComponent/>
  8. integral<10?<>积分不足,无法享用</> : <>{super.render()}<span>vip专享</span></>
  9. }
  10. </div>
  11. }
  12. }
  13. }
  14. 用法:export default Hoc(Params);一般是对别的组件进行渲染。