React 中常用技术
可以少去理解一些不必要的概念,而多去思考为什么会有这样的东西,它解决了什么问题,或者它的运行机制是什么?
1. React 中导出和导入
1.1 ES6 解析
ES6 的模块化的基本规则或特点:
- 每一个模块只加载一次, 每一个 JS 只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。一个模块就是一个单例,或者说就是一个对象;
- 每一个模块内声明的变量都是局部变量,不会污染全局作用域;
- 模块内部的变量或者函数可以通过 export 导出;
- 一个模块可以导入别的模块;
ES6 中 export 和 export default 的区别:
- export 与 export default 均可用于导出常量、函数、文件、模块;
- 你可以在其它文件或模块中通过 import +(常量 | 函数 | 文件 | 模块)名的方式将其导入,以便能够对其进行使用;
- 在一个文件或模块中 export、import 可以有多个,export default 仅有一个;
- 通过 export 方式导出,在导入时要加大括号 { },export default 则不需要。
其实很多时候 export 与 export default 可以实现同样的目的,只是用法有些区别。注意第四条,通过 export 方式导出,在导入时要加 { },export default 则不需要。使用 export default 命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名。
1.2 React 解析
React 中使用 export 导出类可以有两种方法。如下所示。
导出方式1:
// 导出方式1
export default classname
在其他文件中引用时采取如下方式:
import classname form path
示例:
// demo.js 文件
class Welcome extends React.Component{
render(){
return <h1> hello,{this.props.name}</h1>
}
}
function App(){
return (
<div>
<Welcome name="Sara"/>
<Welcome nmae="Peng"/>
</div>
);
}
export default App;
// index.js 文件
import App from './components/Demo';
const element=<App/>;
ReactDOM.render(element,document.getElementById('root'));
导出方式2:
// 导出方式2
export {classname1,classname2}
在其他文件中引用时采用如下方式:
// 注意引用一个类时也要加上{}
import {classname1, classname2} from path
示例:
// Demo.js 文件
class Welcome extends React.Component{
render(){
return <h1> hello,{this.props.name}</h1>
}
}
function App(){
return (
<div>
<Welcome name="Sara"/>
<Welcome nmae="Peng"/>
</div>
);
}
export {Welcome, App};
// index.js 文件
import {App} from './components/Demo';
const element=<App/>;
ReactDOM.render(element, document.getElementById('root'));
2. declare 声明
declare 可以用来声明一个类型、模块、变量以及作用域。声明后其他地方不需要引入,就可以直接使用。
https://www.runoob.com/typescript/ts-ambient.html
https://blog.csdn.net/youhebuke225/article/details/125664535
3. dangerouslySetInnerHTML
dangerouslySetInnerHTML,翻译过来就是:危险的设置内部 HTML。为什么会说危险的设置?要知道有这么一句话:“永远不要相信用户的输入”。用户有时候不会按照程序员所设想的规则来进行数据的输入,比如想要用户输入数字,用户却输入的是文本,类似的情况比比皆是。
(1)dangerouslySetInnerHTML 的作用:
当用户输入或者获取到的数据是一段 HTML 代码的时候,dangerouslySetInnerHTML 就可以把这一段代码变成 HTML,然后插入到某个地方,类似 JS 中的 innerHTML。
即当用户输入的是 HTML 代码时,如果不设置 dangerouslySetInnerHTML 的话,那么在展示的时候就是一段 HTML 代码字符串了。
(2)dangerouslySetInnerHTML 的语法:
dangerouslySetInnerHTML 是作为标签属性出现的,并且是值是一个对象,该对象内有一个属性,也是对象:
<span dangerouslySetInnerHTML = {{
__html: item,
}}>
</span>
__html 是固定写法,后面的 item 表示要插入的内容,如果插入的是 HTML 代码,那么就会以 HTML 进行渲染,然后展示在页面上;如果要插入的内容是其他文本,那么不会有任何影响。
(3)为什么是危险的设置?
不合时宜的插入 HTML 可能会导致网站被某些不良分子进行 XSS 攻击,作者取名 dangerouslySetInnerHTML 也是为了警示程序员,不要随意的使用该属性。
4. immutable
immutable 是一种持久化数据结构,immutable 数据就是一旦创建,就不能更改的数据,每当对 Immutable 对象进行修改的时候,就会创建返回一个新的不可变 immutable 对象,以此来保证数据的不可变。在新对象上操作并不会影响到原对象的数据。这个库的实现是深拷贝还是浅拷贝?
immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,immutable 使用了structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
安装:
npm install immutable
引入:
const immutable = require("immutable")
const { Map } = require('immutable');
具体使用参考官网:
5. Portal 传送门
5.1 概念和应用场景
React 节点默认渲染到父节点下,React Portal 提供了一种将子节点渲染到父组件以外的 DOM 节点的优秀解决方案。
Portal 最常见的适用场景是当子组件需要从视觉上“跳出”其容器时,譬如模态对话框、悬浮卡、提示框、加载动画等。典型的用法就是当父组件的 DOM 元素有 overf1ow:hidden 或者 z-index 样式,而你又需要显示的子元素超出父元素的盒子。举例来说,如对话框,悬浮框,和小提示。
5.2 Portal 构建语法
React Portal 构建语法:
ReactDOM.createPortal(child, container)
第一个参数 child 是可渲染的 react 元素,子符串或者片段等。第二个参数 container 是 Portal 要插入的 DOM 节点元素的位置。
普通的组件,子组件的元素将挂载到父组件的 DOM 节点中:
render() {
// React 挂载一个div节点,并将子元素渲染在节点中
return (
<div>
{this.props.children}
</div>
);
}
有时需要将元素渲染到 DOM 中的不同位置上去,这是就用到的 Portal 的方法。
下面使用 React Portal 创建一个简单 modal 组件:
//此时 React 将子元素渲染到 Dom 节点上。domNode 是一个有效的任意位置的 dom 节点。
const Modal = ({ message, isOpen, onClose, children }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">
<span className="message">{message}</span>
<button onClick={onClose}>Close</button>
</div>,
domNode
);
};
即使 Portal 是在父级 DOM 元素之外呈现的,他的表现行为也跟平常我们在 React 组件中使用是一样的。它能够接受 props 以及 context API。这是因为 Portal 驻留在 React Tree 层次结构内(也就是保证在同一颗 React Tree 上)。
5.3 为什么我们需要它呢?
当我们在特定元素(父组件)中使用模态弹窗时,模态的高度和宽度就会从模态弹窗所在的组件继承,也就是说模态弹窗的样式可能会被父组件影响。传统的模态框需将需要设置 CSS 属性,例如 overflow:hidden
和 z-index
来避免这个问题。
普通组件将导致模态框在 root 下的嵌套组件内部渲染。当使用浏览器检查元素时,如下所示。
接下来,让我们看看 React Portal 是如何被使用的。下面的代码使用 createPortal() 在 root 树层次结构之外创建 DOM 节点来解决此问题。
const Modal = ({ message, isOpen, onClose, children }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">
<span>{message}</span>
<button onClick={onClose}>Close</button>
</div>,
document.body
);
};
function Component() {
const [open, setOpen] = useState(false);
return (
<div className="component">
<button onClick={() => setOpen(true)}>Open Modal</button>
<Modal
message="Hello World!"
isOpen={open}
onClose={() => setOpen(false)}
/>
</div>
);
}
下面显示的是 DOM 树层次结构,这是在使用 React Portal 时得到的,其中模态框组件将被注入 root 之外,并且与 root 处于同一层级。
由于这个模态框是在根层次结构之外渲染的,因此模态框的大小不会因为继承父级组件而被更改。
结论:
在我们需要在正常 DOM 层次结构之外呈现子组件而又不通过 React 组件树层次结构破坏事件传播的默认行为时,React Portal(传送门)会派上用场。比如在渲染模态框,工具提示,弹出消息之类的组件时,这很有用。
在 Portal 中的事件冒泡:虽然通过 Portal 渲染的元素在父组件的盒子之外,但是渲染的 DOM 节点仍在 React 的元素树上,在那个 DOM 元素上的点击事件仍然能在 DOM 树中监听到。
6. navigator.userAgent
navigator.userAgent 是浏览器用于 HTTP 请求的用户代理头的值,通过userAgent 可以取得浏览器类别、版本,客户端操作系统等信息,可以用来判断当前浏览器所处的环境。
在 PC 端打开 ,navigator.userAgent 显示如下:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
在手机 Web 端打开 ,navigator.userAgent 显示如下:
Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
使用场景:
场景1:判断页面是在手机端还是 PC 端打开:
window.location.href=/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)?"http://localhost:8888/mobile_web":"http://localhost:8888/PC";
场景2:判断页面是在手机端,平板端还是 PC 端打开:
var os = function (){
var ua = navigator.userAgent,
isWindowsPhone = /(?:Windows Phone)/.test(ua),
isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
isAndroid = /(?:Android)/.test(ua),
isFireFox = /(?:Firefox)/.test(ua),
isChrome = /(?:Chrome|CriOS)/.test(ua),
isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
isPhone = /(?:iPhone)/.test(ua) && !isTablet,
isPc = !isPhone && !isAndroid && !isSymbian;
return {
isTablet: isTablet,
isPhone: isPhone,
isAndroid: isAndroid,
isPc: isPc
};
}();
if (os.isAndroid || os.isPhone) {
alert("手机" );
} else if (os.isTablet) {
alert("平板" );
} else if (os.isPc) {
alert("电脑" );
}
场景3:获取操作系统类型,判断是 Android 或者 IOS:
/**
* 获取操作系统类型,
* 0 Android
* 1 iOS
*/
function getOSType() {
if (/(Android)/i.test(navigator.userAgent)) {
return 0;
} else if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
return 1;
} else {
return 2;
}
}
场景4:判断当前环境是否是微信环境:
function is_weixin(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=="micromessenger") {
return true;
} else {
return false;
}
}