赞
踩
使用create-react-app
快速搭建一个react脚手架。首先必须全局安装:npm i create-react-app -g
create-react-app --version
可以查看当前安装版本号
使用该命令创建一个项目:create-react-app 项目名
(项目名字需要遵循npm规范),使用该命令创建的react项目是基于webpack完成的。创建完成的目录结构如下
在package.json
文件中默认会安装:react(核心框架)
和react-dom(基于React构建的WebAp,即HTML页面)
。(react-native
构建app)。
react-scripts
是脚手架中自己对打包命令的封装,基于它打包会调用node_modules中的webpack进行处理。(react脚手架为了简洁目录结构,并没有vue的配置文件等,将webpack打包的规则及其插件/loader等隐藏到了node_modules目录下)。
start
是开发环境,本地启动web服务器运行。build
是生成环境,打包部署,将打包的内容放在build目录中。test
是单元测试。eject
暴露webpack配置,供用户修改打包规则。
web-vitals
是性能检测工具
browserslist
是设置浏览器的兼容情况,如postcss-loader+autoprefixer
设置css3相关,babek-loader
设置ES版本兼容。
在react中入口文件名为index,js
。删除部分引入文件保留初始代码如下。<React.StrictMode>
是react专属的严格模式。
import React from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<div>hello</div>
</React.StrictMode>
);
执行命令npm run eject
。控制台给出选项选择,该选项的意思是一旦将配置项暴露出来后就是永久行为,无法复原。
但是这个时候会发现选择yes之后,紧跟着报了错误,这是因为我们之前修改过代码删除过文件,git历史区并没有我们目前修改过代码的记录。解决方法:先将我们修改后的代码提交到git历史区保留备份(防止暴露后的代码覆盖了我们的代码)
执行git步骤
git add -A
:将工作目录中的所有更改(包括新文件、修改的文件和已删除的文件)添加到暂存区,准备进行提交git commit -m 'init'
:提交暂存区中的更改到本地代码仓库,并附带一条简短的提交消息 “init”。完成之后再次执行npm run eject
命令即可config
目录文件(webpack的配置相关信息)和scripts
目录。scripts
目录中结构如图,这里保存的是执行命令的入口文件。如执行npm run start
就会找到该文件中的start.js
文件去执行packjson
文件中的代码也会发送改变这里简单介绍几个:babel-preset-react-app
:原 @babel/preset-env
语法包的重写,实现ES版本转换。重写后可以识别react语法,实现代码转换sass-loader
:使用create-react-app
命令默认会按照sass预编译语言babel.config.js
配置文件,这里是对babel-loader
的额外配置。如果自己后期需要修改babel,直接在这里修改即可,不需要再次创建文件。 "babel": {
"presets": [
"react-app"
]
}
在默认环境下,脚手架给我们安装的是sass-loader
预编译语言,我们可以替换。步骤如下
npm uninstall sass-loader
:先移除旧的预编译器npm i less
和npm i less-loader@8
:安装less预编译,注意版本不能太高,否则不兼容。修改sass为less直接在向外暴露的webpack.config.js
目录中修改即可。具体位置在return返回值中的module配置项内部的rules配置项中的oneOf下
。该位置处理了相关文件信息。在这里修改相关的代码。
首先将页面中原sass
相关的代码全部修改为less
。之后在rule配置项中将sass相关替换。
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
{ test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( 。。。 'less-loader' ), sideEffects: true, }, { test: lessModuleRegex, use: getStyleLoaders( .... 'less-loader' ), },
在return返回中的resolve配置项中的alias中配置路径别名
。(在path.js
文件中已经帮助我们处理指向src目录的方法,只需要调用即可)。
alias: {
'@': paths.appSrc,
}
在src目录下创建一个index.less文件并添加样式,并在index.js
入口文件中使用别名引入。经过测试,页面可以正常按样式显示。
在目录下创建jsconfig.json
添加代码别名提示
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}
默认情况,使用的域名和端口号为:http://localhost:3000/。如果想修改url信息,可以进入scripts/start.js
文件修改。(为什么在这里修改,可以简单理解用户启动本地服务器使用npm run start
命令,该命令会去执行"start": "node scripts/start.js"
最后进入scripts/start.js
,在这里会生成域名信息)
在文件中修改域名信息代码如下,其中process.env.HOST
和process.env.PORT
均为环境变量。可以直接修改后面的默认3000端口号或0.0.0.0域名信息
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0'; //这里修改为127.0.0.1并打开页面查看,发现域名已经修改
如果想使用环境变量去设置域名信息需要安装:npm i cross-env
。然后在package.json
文件中在设置入口路径的时候,直接设置环境变量。
"scripts": {
"start": "cross-env PORT=8080 node scripts/start.js",
}
在browserslist
配置项中实现浏览器兼容,会完成以下步骤
postcss-loader
生效:控制CSS3的前缀babel-loader
生效:控制ES6的转换但是无法处理ES6内容的API兼容问题。在webpack中我们需要借助@babel/polyfill
对常见内置API进行重写。但是在脚手架中不需要手动引入安装,脚手架中已经默认帮助我们安装好了react-app-polyfill
库,该库就是对@babel/polyfill
进行重写。
直接在index.js
入口文件中引入使用,其中stable是提供新语法,以便在旧浏览器中可以运行。ie9和ie11是为了让新特性能够兼容旧浏览器进行的版本兼容处理。*
// 处理浏览器兼容
import 'react-app-polyfill/ie9'
import 'react-app-polyfill/ie11'
import 'react-app-polyfill/stable'
react中处理跨域,是在config/webpackDevServe.config.js
中完成代理。具体代码如下,会先根据paths.proxySetup
读取config/path.js
文件下导出的proxySetup: resolveApp('src/setupProxy.js')
代码部分,该代码的意思是读取src/setupProxy.js
文件处理跨域代理。因此我们需要在在该文件中处理。
proxy,
onBeforeSetupMiddleware(devServer) {
devServer.app.use(evalSourceMapMiddleware(devServer));
if (fs.existsSync(paths.proxySetup)) {
require(paths.proxySetup)(devServer.app);
}
},
创建完成setupProxy.js
文件后需要安装处理跨域的库:npm i http-proxy-middleware
,并引入createProxyMiddleware
方法编写代码如下。这种方式的优点就是可以配置多个代理
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
// 第一个代理地址
app.use(
createProxyMiddleware('/zhi', {
target: "https://news-at.zhihu.com/api/4",
changeOrigin: true,
ws: true,
pathRewrite: { "^/zhi": "" } //重写
})
)
}
之后编写测试代码
fetch('/zhi/news/latest')
.then(res => res.json())
.then(val => {
console.log(val);
})
采用数据推动视图更新,不直接操作DOM元素。
在原生js中操作DOM可能会引起回流和重绘,这样子会降低性能,并且每次操作的时候都需要实现将需要的DOM元素获取,这样子过于麻烦。
数据驱动思想
class Count extends React.Component { state = { num: 0 } render() { let { num } = this.state return <> <span>{num}</span> <br /> <button onClick={() => { num++ this.setState({ num: num }) }}>点击</button> </> } } root.render( <Count></Count> );
在react中构建视图就需要了解什么是JSX。
JSX:JavaScript and Xml。js和xml或html混合 (html是提供好的标签语言,我们直接使用即可。而Xml是自定义标签。需要手动定义)。
在脚手架默认生成的index.js
文件如下,其中既有js代码又有html代码。
import React from 'react'; //引入react框架核心语法
import ReactDOM from 'react-dom/client'; //引入构建HTML(webapp)页面核心
import '@/index.less'
// 获取唯一根容器,将render中的内容最终放到跟容器中,与vue一致
const root = ReactDOM.createRoot(document.getElementById('root'));
let str = 'hello world'
// 基于render方法渲染编写的视图
root.render(
<div>
{str}
</div>
);
在vscode中,默认情况下,会发现在上面代码中的render
函数内部编写html代码片段的时候没有提示。那么如何让vscode能够识别支持JSX语法,即代码提示。
有两种方法让vscode有够实现支持JSX语法
index.js
,先点击vscode底部该标签,然后在弹出的输入框中输入react,选择第一个即可。这个时候html的快捷操作就可以实现了。缺点是每次都需要手动选择。index.js
文件的后缀名修改为index.jsx
,该操作会默认执行方法一。在webpack.config.js
配置规则中,会jsx后缀的文件按照js去处理。如果想在HTML中使用JS表达式,需要使用{}包裹js部分代码。区分Vue的{{}}。在react中{}
中js表达式需要写有返回值的。如下代码中,数组的map方法一定会返回一个值所以不报错,但是forEach不行。且普通的for,for/in等循环会报错。
root.render(
<div>
{[1, 2, 3].map(item => <h1>{item}</h1>)}
</div>
);
注意点:
const root = ReactDOM.createRoot(document.body); //错误
render函数
中,只能有一个根节点,这一点和vue2一致。因此必须使用一个根标签包裹内容。但是这样子可能会额外的添加无用的标签增加层级,这个时候react为我们提供了一个空文档标签或React.Fragment:<></>
或 <React.Fragment></React.Fragment>
。这样子该标签最终不会出现在页面中。查看该博客了解root.render(
<>
<div>{str}</div>
<h1>你好</h1>
</>
);
root.render(
<React.Fragment>
<div>{str}</div>
<h1>你好</h1>
</React.Fragment>
);
如下代码中,给params赋予不同类型的值,查看页面打印效果
let params = 1
root.render(
<>
{params}
</>
);
Infinity
,NaN
,最终页面显示的Infinity
,NaN
,而不是显示空布尔值
,undefined
,null
,Symbol()
,10n
,void 0(特殊的null表达形式)
页面什么也不会显示{}
括弧中直接使用该标签变量的时候,react会直接报错(vue中可以使用)。但是在{params.age}
就没有问题吗,因为这样就是操作一个普通数值数据。let params = { age: 29 }
root.render(
<>
{params}
</>
);
let params = [1, 2, 3] // 若为[1, 2, { age: 2 }]
root.render(
<>
{params}
</>
);
{}
中渲染,会报错,react认为这应该是一个组件,需要参照函数组件是使用方式,即 < params/> 。let params = function () { }
root.render(
<>
{params}
</>
);
new
关键字的特殊情况,如new Number(1)
,new RegExp(/\d+/)
,/\d+/
等会报错。但是new Array(1, 2)
和new Array(5).fill(1)
不会出现问题。(重点了解){}
里面渲染React.createElement()
形式,而该方法会返回一个虚拟DOM对象,这是被允许的,所以可以在JSX语法中使用如下代码:{React.createElement('button', { className: 'btn' }, '按钮')}
。这样子会直接创建一个虚拟DOM对象并最终转化为真实DOM放在页面中。style={{marginRight: spacing + 'em'}}
。(vue无该事项)root.render(
<>
<div style='color:red'>Hello World</div> //错误写法
<div style={{ color: 'red', fontSize: 36 + 'px' }}>Hello World</div >
</>
);
className
替换class
,否则报错。(vue无该事项)<div className='box' style={{ color: 'red', fontSize: 36 + 'px' }}>Hello World</div >
{/* */}
,但是如果是在{}
中添加代码注释,为/* */或//
{}
编写HTML元素,那么会被编译放在页面中。(vue不允许)root.render(
<>
{<button>按钮</button>}
</>
);
let flag = false
let isRunning = true
root.render(
<>
{/* 第一种方法,按钮会被渲染到DOM结构中,但是不会显示出来。类似vue的v-show */}
<button style={{ display: flag ? 'block' : 'none' }}>按钮1</button>
<hr />
{/* 第二种方式,按钮直接不被处理,不会再DOM结构显示 */}
{flag ? <button>按钮2</button> : null}
<hr />
<button>{isRunning ? "正在执行。。。" : "执行完毕"}</button>
</>
);
如果想对一个数组中的内容进行操作的话,可以借助遍历实现。目前知识储备情况下,可以借助数组的map方法实现。
在map方法中,需要返回一个li标签组成的结构,在该结构中需要注意每次遍历的item为对象,不能直接使用,需要item.title使用对象中的普通数据。 最后返回的数组中,每一个元素结构如:[< li>…</ li>,< li>…</ li>],然后在{}
语法中,会自己识别数组中的内容,并将每一个元素分别渲染到页面上
let arr = [ { id: 1, title: "今天天气很好" }, { id: 2, title: "今天天气很差" }, { id: 3, title: "今天下雨了" }, ] root.render( <> <div className='box'> <h3>新闻列表</h3> <ul> {arr.map((item, index) => { // react的遍历和vue一样都需要绑定唯一key值,做虚拟dom和真实dom的比对 return <li style={{ listStyle: 'none' }} key={item.id}> <em>{index + 1}</em> <span>{item.title}</span> </li> })} </ul> </div> </> )
new Array(5)
:只指定一个数值的时候,会创建长度为5的空数组
let a1 = new Array(5,1)
:会创建包含两个数组元素
let a2 = new Array('5')
:创建长度为1包含字符的数组
第一种情况就是稀疏数组,后面的为密集数组。当使用数组的forEach/map等方法的时候,他们不会去迭代稀疏数组。数组的fill
方法用来填充数组
{new Array(5).fill(null).map((_, index) => {
return <button key={index}>按钮{index + 1}</button>
})}
let str = 'hello world';
root.render(
<>
<h1>{str}</h1>
<div>
{['小王', '大王'].map(item => <p>{item}</p>)}
</div>
</>
);
在package.json文件中babel-preset-react-app
包负责对@babel/preset-env
的重写。重写的时候加入了负责支持JSX语法的编译。在babel官网中可以进行测试,查看JSX代码是如何被转换的。
<>
<h1 className='title' style={{ color: 'red' }}>{str}</h1>
<div className='box'>
<span>{x}</span>
<span>{y}</span>
</div>
<div></div>
</>
转换代码如下
React.createElement( React.Fragment, //即<></>空文档标签 null, React.createElement( "h1", { className: "title", style: { color: 'red' } }, str), React.createElement( "div", { className: "box" }, React.createElement( "span", null, x), React.createElement( "span", null, y) ), React.createElement("div", null) );
分析
babel-preset-react-app
实现,将JSX语法转换为React.createElement(...)
格式。只要是元素标签,都会调用createElement()
。并且每一个React.createElement(ele,props,...children)
都需要传入对应的参数信息。createElement()
方法需要创建的标签名,为第一个参数项React.createElement(...)
方法创建的为虚拟DOM—virtualDOM,也称为JSX对象或JSX元素或ReactChild对象,该方法会返回一个虚拟DOM对象结构。将上面的代码打印输出。export function createElement(ele, props, ...children) { let virtualDOM = { $$typeof: Symbol('react.element'), key: null, ref: null, type: null, //初始为空 props: {},// 默认为一个对象 } virtualDOM.type = ele; // 判断标签的属性是否存在 if (props) { virtualDOM.props = { ...props } } let len = children.length if (len === 1) { //如文本信息,数值信息 virtualDOM.props.children = children[0] } if (len > 1) { // 多个子元素,则为数组 virtualDOM.props.children = children } return virtualDOM }
到这一步,基本输出格式就和react提供的一模一样。
将虚拟DOM最终转换为真实DOM,都是通过render
方法实现的,但是在不同版本的react中,书写格式是不同的。
在react18版本中
const root = ReactDOM.createRoot(document.getElementById('root')); //获取根容器
root.render( //通过react.createRoot().render()转换为真实DOM
<>...</> //创建虚拟DOM,调用React.createElement()
)
但是在react16版本中,代码如下
ReactDOM.render(
<>...</>,
document.getElementById('root')
)
首先基于react16版本写一段render
方法,大体思路:传染两个参数,第一个为虚拟DOM,第二个为容器,主要难处理的是如何第一个参数中的标签属性添加到标签身上,因为所有的标签属性跟children都存放在了一个props对象中,所以需要遍历取出来。那么如何遍历一个对象的属性这里需要扩展一个又关for/in
循环的弊端。
一般情况下,一个对象的属性打印输出为深红色和浅红色,而深色代表可枚举的属性,而浅色代表不可枚举的属性(不可枚举代表不能被for/in或Object.keys()取出)。通常内置的属性均为不可枚举,自定义属性是可以枚举的。 但是我们可以使用Object.defineProperty
方法修改成员的枚举性。
将设有一个数组对象
Array.prototype.BB = 'bb'
let arr = [1, 2] //定义的私有成员
arr[Symbol('3')] = 3 //定义的私有成员
console.log(arr);
for/in
循环弊端:性能差,因为会迭代所以公共或私有的成员,并且只能迭代可枚举的,非Symbol类型的值。,下面这段输出就代表了for/in循环的弊端
for (let i in arr) {
console.log(i); //输出0 1 BB
}
我们需要自己借助相应的方法替代for/in循环,获取所有的私有属性相应的方法如下
Object.getOwnPropertyNames(arr) //只获取一个对象的私有属性,不包括Symbol类型
Object.getOwnPropertySymbols(arr) //只获取一个对象的私有属性并且为Symbol类型
//然后调用两个方法,将返回的结果拼接一起即可
let keys = [...Object.getOwnPropertyNames(arr), ...Object.getOwnPropertySymbols(arr)]
或者
let keys = Object.getOwnPropertyNames(arr).concat(Object.getOwnPropertySymbols(arr))
//输出结果
keys.forEach(key => console.log(key, arr[key])) //Symbol为字符串,不能再次添加字符串包裹
let keys = Reflect.ownKeys(arr) //获取私有成员,不论是不是Symbol类型
最后封装一个方法代替forin循环
function each(obj, callback) { // null也为object类型,需要先判断 if (obj === null && typeof obj !== 'object') throw new ('obj is not a object') if (typeof callback !== 'function') throw new ('callback is not a function') let keys = Reflect.ownKeys(obj) keys.forEach(key => { let value = obj[key] callback(key, value) }) } Array.prototype.BB = 'bb' let arr = [1, 2] //定义的私有成员 arr[Symbol('3')] = 3 //定义的私有成员 each(arr, (key, value) => { console.log(key, value) })
那么如何将一个对象的信息添加到一个DOM元素结构上又存在两种不同的方法,且作用各不相同
方法一,假设页面有一个id为root的容器,为一个元素设置属性,可以直接采样对象.属性
的方法添加。但是这种方法中,自定义属性无法在页面标签结构中显示添加的属性,所以的属性均被添加到堆内存中,但是如果添加到内置属性时,会设置在元素的标签身上,同时堆内存不在添加。
使用setAttribute
方法实现,直接将新属性设置在元素的页面结构中,同时堆内存中不在添加
let str = 'hello world'; let x = 10; let y = 20; let JSXObj = ( createElement( "div", { className: "container" }, createElement( "h1", { className: "title", style: { color: 'red' } }, str), createElement( "div", { className: "box" }, createElement("span", null, x), createElement("span", null, y))) ); render(JSXObj, document.getElementById('root'))
// 获取对象属性 function each(obj, callback) { // null也为object类型,需要先判断 if (obj === null && typeof obj !== 'object') throw new ('obj is not a object') if (typeof callback !== 'function') throw new ('callback is not a function') let keys = Reflect.ownKeys(obj) keys.forEach(key => { let value = obj[key] callback(key, value) }) } export function createElement(ele, props, ...children) { let virtualDOM = { $$typeof: Symbol('react.element'), key: null, ref: null, type: null, //初始为空 props: {},// 默认为一个对象 } virtualDOM.type = ele; // 判断标签的属性是否存在 if (props) { virtualDOM.props = { ...props } } let len = children.length if (len === 1) { //如文本信息,数值信息 virtualDOM.props.children = children[0] } if (len > 1) { // 多个子元素,则为数组 virtualDOM.props.children = children } return virtualDOM } export function render(virtualDOM, container) { let { type, props } = virtualDOM if (typeof type === 'string') { // 不对<></>进行处理,即Symbol类型 // 创建标签 let ele = document.createElement(type) // 给标签添加属性 each(props, (key, value) => { // 处理JSX中的className类名情况 if (key === 'className') { ele.className = value //原生设置类名的方式 return } //设置样式属性,JSX中的样式属性格式 style={{color:'red',fontSize:'18px'}} if (key === 'style') { each(value, (styKey, styVal) => { ele.style[styKey] = styVal }) return } // 处理children属性的情况 if (key === 'children') { // 可能为数组又可能不是数组,值可能是节点,又可能是文本信息 // 所有的节点都是插入当前ele下 let children = value //保存值 if (!Array.isArray(children)) { children = [children] //强制文本内容转换为数组,方便比较 } children.forEach((child) => { // 第一种使用正则判断 // if (/^(string|number)/.test(typeof child)) if (typeof child === 'string' || typeof child === 'number') { // 代表只是字符串文本,创建文本节点,直接插入 let textNode = document.createTextNode('child') // 插入当前元素中 ele.appendChild(textNode) return } // 否则为虚拟DOM,继续递归调用render函数 render(child, ele) }) return } //普通情况 如name='root' ele.setAttribute(key, value) //给标签设置属性 }) // 插入容器中 container.appendChild(ele) } }
最终页面效果
React中组件没有全局和局部概念,将需要使用的组件注册到React上。组件分为以下三类
函数组件:在src目录下,根据需求创建一个文件夹存放编写的组件名.jsx
组件,在该文件中编写一个函数,该函数必须有返回值,且返回值为JSX视图,最后将整个函数导出。之后在需要引入的地方使用,在render
函数中使用自闭和或双标签的形式使用。组件的名字推荐使用大驼峰
函数组件不具备状态,生命周期钩子,ref等。第一次渲染完毕后数据不会更新视图,除非父组件重新调用渲染。并且函数组件属于静态组件。
export const Demo1 = () => {
return <div className="demoBox">
<h1>我是demo1</h1>
</div>
}
在使用该组件标签的时候,最终也是基于React.createElement
方法创建虚拟DOM。同时可以给改标签绑定自定义属性使用。
传递参数的时候需要注意遵循React的语法:使用{}包裹中需要传递的JS部分,如果只是一个字符串,则不用{},这里类似vue的 :x='10’的写法,
import { Demo1 } from './views/Demo1';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Demo1 title="我是demo1" x={10} y={[1, 2, 3]} obj={{ age: 666 }} className="test" style={{ color: 'red' }}></Demo1>
);
该代码最终会被转换为如下格式,打印后输出如下会发现,原来type
字段表示标签名的地方现在变成一个函数了,最终会去调用这个函数返回组件的虚拟DOM。
React.createElement(Demo1, {
title: "\u6211\u662Fdemo1",
x: 10,
y: [1, 2, 3],
obj: {
age: 666
},
className: "test",
style: {
color: 'red'
}
});
执行的基本步骤
babel-preset-react-app
将JSX语法转换为createElement
方法格式createElement
方法执行该函数,该函数会返回一个虚拟DOM对象,对象的格式上图所示,重点是type
变为组件函数render
函数中,会根据生成的虚拟DOM,先去执行type
所对应的函数。同时将当前组件标签定义的属性(即上图的props)传递给该函数。render
函数会将根据组件标签返回的虚拟DOM渲染为真实DOM放在容器中。根据上面的内容可知,如果给一个组件标签设置属性,那么在在转换为虚拟DOM的时候,会将定义的属性传递给函数组件。
root.render(
<Demo1 title='我是demo1' x={10} y={[1, 2, 3]} obj={{ age: 666 }} className="test" style={{ color: 'red' }}>
<div>你好</div>
</Demo1>
);
我们定义变量接收定义的属性,然后我们尝试修改title
属性的值,这个时候会发现,控制台报错,代表传递给函数组件的属性参数,只能读,不能修改,这一点和vue一致。原理是将传递来的对象参数冻结了!!!
export const Demo1 = (props) => {
props.title = 'hello'
return <div className="demoBox">
<h1>我是demo1</h1>
</div>
}
关于上面的报错信息扩展对象的规则设置
冻结:被冻结的对象不能再被更改:不能修改现有属性,不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定
*:使用Object.freeze()
冻结一个对象
*:使用Object.isFrozen()
检测一个对象是否被冻结,通过打印测试函数组件中参数是否冻结,结果为true,代表是冻结后的参数
密封:不能添加新属性、不能删除现有属性或更改其可枚举性和可配置性、不能重新分配其原型。但是现有属性的值是可写的,它们仍然可以更改。
*:Object.seal()
设置一个对象的密封性
*:Object.isSealed()
判断一个对象是否密封
不可扩展:处理不能添加新的属性,其他均能实现
*:Object.preventExtensions()
设置一个对象为不可扩展
*:Object.isExtensible()
检测一个对象是否可扩展
被冻结的对象,包含了不可扩展和密封。同理密封包含了不可扩展
父组件传递给子组件的参数,在子组件中可以设置参数的规则,(这里跟vue的props配置项一致,均可设置父传子的参数信息,如默认值,类型等,但是并不是所有的配置都可以直接设置,需要借助插件)
假设有如下代码,x在第二个参数中并没传递,那么在复用函数组件的时候就会为undefined
<>
<Demo1 title='标题1' x={10}>
</Demo1>
<Demo1 title='标题2' ></Demo1>
</>
export const Demo1 = (props) => {
return <div title={props.title}>
<h1>{`${props.title}:${props.x}`}</h1>
</div>
}
通过把函数当做对象,设置静态的私有属性方法,来给其设置属性的校验规则,设置:defaultProps静态属性
Demo1.defaultProps = {
x: 0
}
当然也可以在传参的时候解构其值,同时指定参数的默认值
export const Demo1 = ({ title, x = 0 }) => {}
如果想指定其他默认值,如数据的类型,是否必传,需要借助prop-types
库。
设置:propTypes静态属性
import PropTypes from 'prop-types'
。。。。。
Demo1.propTypes = {
title: PropTypes.string.isRequired, //类型为字符串,且必传
x: PropTypes.number //类型为数值
y: PropTypes.array, //类型为数组
z: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) //类型可以为字符串或数值
}
首先需要知道,位于组件标签中的元素属于子节点,都会被存放到一个children
属性中保持,但是不同情况下,children
字段的值可能不一样。所有使用插槽传递的子节点都会经过createElement方法转换为虚拟DOM传递到函数参数中
有如下三种情况
children
中children
中保存,(不组成数组)children
字段直接为undefined <>
<DemoSlot title='hello world'> //两个子节点
<h2>我是子节点1</h2>
今天天气很棒
</DemoSlot>
<DemoSlot title='你好,世界'> //一个子节点
<h2>我是子节点2</h2>
</DemoSlot>
<DemoSlot title='你好,世界' /> //无子节点
</>
总而言之如果只是灵活性的更改内容,那么直接做为标签属性传递即可,如果是复杂的结构,可以使用插槽传递,灵活性更高,不需要在组件中写死。
在React中,需要自己处理一些插槽的机制, 在上面的代码中,需要针对不同的children
值去进行判断处理。
如下一段例子,将插槽中的第一个元素放到首部,将第二个元素放到尾部显示。如果children
为数组的情况下,那么没有问题,但是如果是非数组或者undefined的时候则会出错,因此需要进行类型判断,并全部转换为数组。
//组件函数中代码
let { title, children } = props
if (!children) {
children = []
} else if (!Array.isArray(children)) {
children = [children]
}
return <div>
{children[0]}
<hr />
<h1>{title}</h1>
<hr />
{children[1]}
</div>
但是每次都这样子判断的话过于麻烦,所以我们可以借助import React from 'react'
中提供的react核心语法实现上面的过程。在该对象中提供了一个Children
配置项,该配置项中提供了封装好的方法,已经帮我们将上面的情况进行了处理。对于常见的forEach方法处理了,使用可能会和普通的有区别。
在这里调用React.Children.toArray(children)
传入参数,每次都会将该值转换为数组,无论是什么情况,最后都会返回一个新数组。
// 修改后的代码简化为一句即可
children = React.Children.toArray(children)
上面这种情况就是默认插槽的使用方法,接下来介绍带有名字的插槽使用
在React中使用具名插槽就是给标签起一个名字区分。,如下代码的作用是将页眉和页脚固定放在首部,其余的内容均放在主体部分展示。如果按照之前的默认插槽的使用方法,那么这么多的标签组成的children
数组长度是超过2的,不好处理在页面中。
<DemoSlot title='hello world'>
<div slot='header'> //slot是自定义的标识,非内置
<h2>这是页眉部分</h2>
</div>
<div>
<h2>这是内容部分1</h2>
</div>
<div>
<h2>这是内容部分1</h2>
</div>
<div slot='footer'>
<h2>这是页脚部分</h2>
</div>
</DemoSlot>
export const DemoSlot = (props) => { let { title, children } = props // children由多个虚拟DOM组成的数组对象 children = React.Children.toArray(children) let headerSlot = [], footerSlot = [], defalutSlot = [] children.forEach(child => { // 每一个child均为一个虚拟DOM对象,每一个对象都存放props保存自身的属性 let { slot } = child.props if (slot === 'header') { headerSlot.push(child) } else if (slot === 'footer') { footerSlot.push(child) } else { defalutSlot.push(child) } }) return <div> {headerSlot} <hr /> <h1>{title}</h1> 这里是内容部分 {defalutSlot} <hr /> {footerSlot} </div> }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。