组件化开发
组件化+工程化
React中的组件化开发
在Vue2中,其组件的划分有两大思路:
命名思路:
。TableList
。方法名
或属性名
。思路一:全局组件与局部组件,目前公司项目中划分的主要模式。
首先要创建一个 XxxXxx.vue 的单文件组件。
VoteDemo
;全局组件:
Vue.component('XxxXxxx', 组件)
这样在任何一个组件中,都可以直接调用这个组件!
import VoteDemo from '...'
Vue.component('VoteDemo', VoteDemo)
局部组件:
//@3 调用组件
思路二:函数组件
与 类组件
VueComponent
的一个实例。
this.xxx
访问Vue.prototype
上的公共属性方法。
在React中,其组件不分全局和局部的,或者说都是局部组件。
都得引入才能使用。
import VoteDemo from './views/VoteDemo'//@1 导入组件。
//@2 直接调用。
React中的组件,分为以下几种:
React中的函数组件:
.vue
文件,就是为了在该文件内部创建一个单文件组件。
.jsx
文件,就是为了在该文件内部创建一个单文件组件。
.jsx后缀
的文件就表示是有React组件返回,以.js后缀
的就是没有React组件的。
.js后缀
的依旧可以创建组件,就是没提示功能。.jsx后缀
的依旧可以不创建组件。.jsx后缀文件
。.js后缀文件
。VirtualDOM
。(PascalCase)这种模式
调用,不支持模式
。
babel-preset-react-app
与React.createElement
把其变为virtualDOM
。
type
普通函数/构造函数。
props
包含了调用组件时设置的属性。
props
中会新增一个children
字段,存储子节点的信息。
一个值
或者一个数组
。props
对象是只读的,实际上就是被冻结的。
所以函数式组件是不能修改的,会报错。
let vd = React.createElement(
DemoOne,
{
title: "\u54C8\u54C8\u54C8",
x: 10
},
React.createElement("span", null, "\u563F\u563F\u563F")
)
console.log(Object.isFrozen(vd.props))
key/ref
…
render()
对virtualDOm进行渲染。
如果type是一个普通函数,说明调用的是函数组件。
把函数执行,把解析出来的props
作为实参传递给函数。
接收函数执行的返回值,一般是一个新的VirtualDOM
,最后再把这个返回值
进行渲染
,渲染为真实DOM
。
import React from 'react'
import ReactDOM from 'react-dom/client'
import DemoOne from './views/DemoOne'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<>
嘿嘿嘿
哇咔咔
>
)
如果type是一个构造函数,说明调用的是类组件,而且是继承了React.Compontent/PureCompontent的类组件。
React.StrictMode标签
标签
可以开启内部被包裹React组件的严格模式。
目前不建议使用的React老语法
。
标签
移除。const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
嘿嘿嘿
哇咔咔
)
查看项目版本号
npm view xxx versions
一个项目版本号列表大概为:
版本号-版本后缀.后缀阶段数字
主版本号.副版本号.补丁包版本号
主版本号
副版本号
补丁包版本号
版本后缀
一般有以下几个词。
alpha
内测版。beta
公测版。rc
预发版。stable
正式版或稳定版。后缀阶段数字
数字依次递增,数值越大,表示越后发布,越新。版本号-版本后缀
3.0.2
主版本号.副版本号.补丁包
3.0.2-版本后缀
后缀的意思:
alpha 内测
beta 公测
rc 预发
stable 正式版(稳定版)
没有后缀版
一个完整的版本号:
主版本号.副版本号.补丁包版本号-版本后缀.后缀阶段数字
。3.2.0-beta.7
表示:主版本号为3,副版本号为2,补丁包版本为0,处于公测第7轮次的项目版本
。// render:把虚拟DOM变为真实DOM
export const render = function render(virtualDOM, container) {
let { type, props } = virtualDOM
// 创建元素标签(真实DOM)
if (typeof type === 'string') {
let elem = document.createElement(type)
//....
container.appendChild(elem)
}
// 这是一个组件
if (typeof type === 'function') {
// 如果是类组件
if (type.prototype.isReactComponent) {
let inst = new type(props)
//....
let newVirtualDOM = inst.render()
render(newVirtualDOM, container)
return
}
// 如果是函数组件
let newVirtualDOM = type(props)
render(newVirtualDOM, container)
}
}
如何创建一个函数组件
JSX元素(VirtualDOM)
。
属性规则设置:
依赖官方插件 prop-types 「$ yarn add prop-types」
https://github.com/facebook/prop-types
组件.propTypes = {
...
}
/*
如何创建一个函数组件
+ 创建一个函数
+ 函数返回的是一个“JSX元素(VirtualDOM)”
props接收传递进来的属性(含子节点)
+ 被冻结的「只读的」,如果需要修改,我们可以把属性值赋值给其他的变量/状态,以后基于修改变量/状态来改值
+ 虽然不能直接修改props,但是我们可以对props进行规则校验
设置默认值:组件.defaultProps = { ... }
属性规则设置:
依赖官方插件 prop-types 「$ yarn add prop-types」
https://github.com/facebook/prop-types
组件.propTypes = {
...
}
备注:props中的信息是在创建VirtualDOM的时候就获取到了,而规则校验是在函数执行的时候,把传递进来的props中的每一项,按照规则进行校验的,所以即便不符合校验规则,也仅仅是在控制台输出Warning警告错误,但是不影响props中的值!
在Vue框架中,我们可以基于 和 v-slot(简写#) 来处理插槽信息(而且有具名插槽和作用域插槽);React默认是不具备插槽这个概念的,但是真实项目中,插槽还是有用的!
插槽作用:把外界的一些视图,基于插槽传递到组件内部渲染,以此来增强组件的复用性!
具体处理办法:基于 props.children(存储了调用插槽时设置的子节点)
+ 如果组件只预留一个插槽,则直接在插槽位置,渲染 props.children 即可!
+ 但是如果预留了很多插槽,则需要给插槽设置名字,后续调用的时候,指定名字,让其渲染到组件内部的特定位置
我们可以把传递的 children 做特殊的处理
我们可以基于 React.Children 中提供的方法来处理插槽信息
*/
import React from 'react'
import PT from 'prop-types'
const DemoOne = function DemoOne(props) {
let x = props.x,
children = React.Children.toArray(props.children),
header = [],
footer = []
x = 1000
children.forEach(item => {
if (item.props.name === 'slot1') {
header.push(item)
}
if (item.props.name === 'slot2') {
footer.push(item)
}
})
return
{header}
纯函数组件 {props.title} && {x}
{footer}
}
// 设置传递属性的默认值
DemoOne.defaultProps = {
x: 0,
y: false
}
// 设置其它的规则「必传、类型...」
DemoOne.propTypes = {
title: PT.string.isRequired, //字符串类型 & 必须传
x: PT.oneOfType([ //多类型
PT.number,
PT.string
]),
// y: PT.bool,
customProp(props) { //自定义校验规则
if (typeof props.y !== 'boolean') {
return new Error(`y is not a boolean`)
}
}
}
export default DemoOne
它是一个静态组件:第一次渲染组件后,无法基于组件内部的某些操作,让组件再次更新。
想让函数组件再次更新,只能重新调用该函数,并且传递新的属性值进来,以完成不一样的初始化。
index.jsx
import DemoOne from "./views/DemoOne";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
嘿嘿嘿
哇咔咔
>
);
setTimeout(() => {
root.render(
<>
>
);
}, 2000);
DemoOne.jsx
import React from 'react'
import PT from 'prop-types'
const DemoOne = function DemoOne(props) {
let x = props.x,
children = React.Children.toArray(props.children),
header = [],
footer = []
// x = 1000
children.forEach(item => {
if (item.props.name === 'slot1') {
header.push(item)
}
if (item.props.name === 'slot2') {
footer.push(item)
}
})
return {
x = 2000
console.log(x)
}}>
{header}
纯函数组件 {props.title} && {x}
{footer}
}
// 设置传递属性的默认值
DemoOne.defaultProps = {
x: 0,
y: false
}
// 设置其它的规则「必传、类型...」
DemoOne.propTypes = {
title: PT.string.isRequired, //字符串类型 & 必须传
x: PT.oneOfType([ //多类型
PT.number,
PT.string
]),
// y: PT.bool,
customProp(props) { //自定义校验规则
if (typeof props.y !== 'boolean') {
return new Error(`y is not a boolean`)
}
}
}
export default DemoOne
函数组件中有props属性,但是不具备:状态、钩子函数等内容。
函数组件有一个好处:渲染速度快,不需要像类组件一样,处理很多事件。
如何创建一个类组件
但是这个新创建的类必须继承 react中的Component或者PureComponent类。
import {Component,PureComponent} from 'react'
class DemoTwo extends Component{
}
继承的目的
必须
在子类的原型对象上设置 render() 函数,让其返回需要构建的视图virtualDOM
;
当基于render方法渲染类组件的时候,会基于new创造类组件的一个实例,此时在React内部,会历经一系列的处理步骤 —> 组件第一次渲染。
class 语法
class Xxx {
// 给实例设置私有的
constructor(x) {
this.x = x
}
y = 20 //this.y=20
fn = () => { } //this.fn=()=>{}
// 给实例设置公有的「设置类原型对象上的」
say() { }
write() { }
// 把其作为普通对象,设置属性和方法「和实例是没关系的」
static n = 10 // Xxx.n
static setN() { } // Xxx.setN()
}
Xxx.prototype.AAA = 100
组件的创建
组件的更新
/* render:把虚拟DOM变为真实DOM */
export const render = function render(virtualDOM, container) {
let { type, props } = virtualDOM
// 创建元素标签(真实DOM)
if (typeof type === 'string') {
let elem = document.createElement(type)
Object.keys(props).forEach(key => {
let value = props[key]
if (key === 'className') {
elem.setAttribute('class', value)
return
}
if (key === 'style') {
// value:样式对象,我们需要分别赋值元素的 style 行内样式
_.each(value, (styV, styK) => {
elem.style[styK] = styV
})
return
}
if (key === 'children') {
// 处理元素的子节点,value 是children属性的值
let children = !Array.isArray(value) ? [value] : value
children.forEach(child => {
// child:每个子节点「文本|元素」
if (typeof child === 'string') {
// 文本子节点
let textNode = document.createTextNode(child)
elem.appendChild(textNode)
return
}
// 元素子节点:递归处理
render(child, elem)
})
return
}
elem.setAttribute(key, value)
})
container.appendChild(elem)
}
// 这是一个组件
if (typeof type === 'function') {
// 如果是类组件
if (type.prototype.isReactComponent) {
let inst = new type(props)
//....
let newVirtualDOM = inst.render()
render(newVirtualDOM, container)
return
}
// 如果是函数组件
let newVirtualDOM = type(props)
render(newVirtualDOM, container)
}
}