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表达式)
}
<script type="text/babel">
function Box(props) {
return (
<div>欢迎学习{props.content}</div>
)
}
const App = (props) => {
return (
<div>欢迎学习{props.content}</div>
)
}
//ReactDOM.render(<App content="react组件"></App>, document.querySelector("#app"));
ReactDOM.render(App({ content: "react组件" }), document.querySelector("#app"));
</script>
2.拓展-react新特性hook
- 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
- 普通组件来说,他的数据都来自于外部(props),这个组件则是受控组件
- 表单元素是一个特例(表单中的value不能独立出现)。他的值若来自于state,这个组件就是受控组件。
注意事项:
value需要和onChange同时出现(通过setState可以实现v-model的效果);如果只是静态的显示可以使用defaultValue (或者是defaultChecked->只读状态)
3.组件之间的交互
父传子: 通过属性传递,props接收
props传给子组件的值不能由子组件私自修改子传父: 回调函数
子组件调用父组件传递过来的方法来实现的。需传递的数据放到参数里去
—-即子组件函数的返回值是父组件的函数名,通过函数名执行父组件的函数 或者 返回值是父组件函数的参数
this.props.parent事件(this.child事件)
- 直接给子组件绑定ref: this.refs.child子组件来获取子组件的状态和事件的执行。例如:this.refs.子组件.state.xxx 和 this.refs.子组件.事件()
子与子通信,通过父组件作为枢纽(中间人模式)
发布订阅模式
非父子通信使用Context来进行通信: react自带模块,且redux依赖于Context
Context博客地址
cnpm i pubsub-js -S
非父子组件传值: 订阅者的回调函数放在Pubsub,发布者执行订阅者的回调函数
发布/触发:PubSub.publish("事件名","数据");
订阅/监听:PubSub.subscribe("事件名",(msg,data)=>{
//msg是事件名,data是传过来的参数
})
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做数据类型的规范
资料来源https://www.npmjs.com/package/prop-types 利用第三方prop-types来处理 让props看起来有数据类型约束(js是弱语言没有数据类型的概率)
安装: npm install —save prop-types
引入: import PropTypes from ‘prop-types’;
使用
static propTypes={
usename: PropTypes.string,
//如类型不匹配:运行代码,结果不会错页面效果也有,但是控制台会报错
}
/*
optionalArray: PropTypes.array,
optiona1Boo1: PropTypes.boo1,
optiona1Func: PropTypes.func,
optiona1Number: PropTypes.number,
optiona10bject: PropTypes.object,
optiona1String: PropTypes.string,
optiona1Symbo1: PropTypes.symbo1,
*/
7.props的默认值和限制类型
es3开始 es4(偏向java强语言类型-没有特点-废弃) es5(map,Object.definePrototype) es6-箭头 es7-static
//1.默认值
类组件:static defaultProps={key键:默认值};
无状态组件:组件名.defaultProps={key键:默认值};//prototype
//2.限制类型
import PropTypes from "prop-types";// 用的是PropTypes内的方法来验证。其实也可以自己写方法来验证,但是它包含了方法,用就行了
//不用安装,脚手架自带。文档:https://www.npmjs.com/package/prop-types
{/* 类组件(两种方法都支持-一般写第一种); 无状态组件似乎只支持第二种 */}
方法1. (es7的写法static加静态属性):static propTypes={key键:PropTypes.类型(小写).isRequired(必传)};
方法2. (es6写法加静态属性):
组件名.propTypes={key键:PropTypes.类型(小写).isRequired(必传)};//prototype(静态属性或类属性-不用实例化)
8.异步数据的传值
<p>异步数据的请求处理方法</p>
1. <p>swiper-轮播图:先请求数据,再在更新阶段钩子函数实例化swiper(或者在setState和axios的回调里实例化)</p>
优雅地利用函数的副作用(此处的副作用是干其他事)
<Swiper />
<hr />
2. 类似于v-if
<p>swiper-轮播图(好处:解耦分离):onlyswiper只管swiper的实例和布局;数据由父组件请求到了之后通过props子组件(三目判断有数据后再渲染)</p>
<Myswiper />
<hr />
生命周期
react核心算法由fiber(diff比对仍然存在,只优化了渲染)替代diff。因为的渲染可以被打断,导致带will的钩子函数就不安全
// 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() | 必需的,被调用时先计算props 和state ,并return返回(React元素 |
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(当年对拦截没有尝试,而是自己设置更改状态的方式)
export default class Index extends React.Component({
{/* class类函数的一般写法 */}
myname = '直接定义变量'
state = {} // 直接这样定义的state- 还是用构造器吧
say(){console.log('构造函数内定义函数')}
say2=()=>{console.log('箭头')}
{/* 构造器,抄袭的java,是一个语法糖 */}
constructor(props) {
super(props)// 继承自React.Component则必须写,不管有没有props
this.state = {
}
console.log(props)
}
})
高阶组件
概念: 我们把 参数是组件 返回值也是组件 这类组件我们叫 “高阶组件”(HOC)
本质是高阶函数 (map filter forEach ….)
0.高阶组件withRouter
路由切换的组件才能拿到this.props中路由的3个属性。但是经过withRouter(一般最外层)返回的高阶组件可以是组件拥有路由的3个属性。
1.属性代理
高阶组件中返回的组件一定要传递props,不然被包裹的组件拿不到props,传递之后返回的组件才能进行父子属性传值。下面举例:
进入一个组件,验证是否登录,没登录,先记下当前的地址,在重定向到login,登陆之后跳转到记录的地址 - hoc或者权限路由(三目; if判断; &&符)
import React from 'react'
import { Redirect } from 'react-router-dom'
export default function Hoc(BackComponent){
// 其实props传递给我(return的class组件)了,我要继续往下面传递给返回的组件--即属性代理
return class extends React.component{
render(){
sessionStorage.setItem('page',this.props.location.pathname)
if(sessionStorage.getItem('user')){
// 扩展运算符传递所有props,经过hoc返回的组件才能够进行父子属性传值
return <BackComponent {...this.props} />
}else{
return <Redirect to="/login" />
}
}
}
}// 没登录则这个组件无法访问
// 可省略高阶组件的名字Hoc直接导出; 也可省略return的类组件的名字Test(直接retur和导出)
2.反向继承
继承自参数的这个组件; 调用super.render()
// 反向继承或渲染劫持 - 调用super.render()
export default (BackComponent, integral = 10) => {
// 1.不在继承自React.Component,而是继承自返回的组件
return class extends BackComponent {
render () {
return <div>
{ // super.render()调用继承的返回组件的render,即DOM内容 ===等价于渲染<BackComponent/>
integral<10?<>积分不足,无法享用</> : <>{super.render()}<span>vip专享</span></>
}
</div>
}
}
}
用法:export default Hoc(Params);一般是对别的组件进行渲染。