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');

具体使用参考官网:

https://github.com/immutable-js/immutable-js

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:hiddenz-index 来避免这个问题。

普通组件将导致模态框在 root 下的嵌套组件内部渲染。当使用浏览器检查元素时,如下所示。

8. Portal 1

接下来,让我们看看 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 处于同一层级。

9. Portal 2

由于这个模态框是在根层次结构之外渲染的,因此模态框的大小不会因为继承父级组件而被更改。

结论:

在我们需要在正常 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;
    }
}
posted @ 2023-11-20 20:08  背包の技术  阅读(171)  评论(0编辑  收藏  举报