react 的特点
声明式
: 声明式编写 UI,易于维护组件化
: 组件逻辑使用js而非模版,轻松传递数据,保持状态和DOM分离跨平台
:可以在其他语言基础上引入react,使用node服务渲染,React Native 开发原生移动应用
render
功能
:
- 接受输入的数据,并返回要展示的内容;
- 当状态数据改变时,组件会调用,并重新渲染对应的标记
dangerouslySetInnerHTML
功能
:
- React 为浏览器 DOM 提供 innerHTML 的替换方案,存在安全隐患
Cross-Site Scripting(跨站脚本攻击)简称
XSS
,是一种代码注入攻击
,注入XSS 代码到网页,窃取用户数据并发送到攻击者的网站。
React 如何防止 XSS 攻击
: 引申
- 自动转义,React DOM 在渲染所有输入内容之前,默认会进行转义;
- 改造React.createElement()的Children,防止存储型的 XSS 攻击。
React简单引入
<!-- 加载 React-->
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<!-- script标签内使用 JSX,type="text/babel" -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
</script>
React命令安装
npx create-react-app demo
生成压缩文件
- 安装node
- 运行 npm init -y
- 运行 npm install terser
- 运行 npx terser -c -m -o xxx.min.js – xxx.js
将 JSX 添加到项目(安装 JSX 预处理器)
- npm install babel-cli@6 babel-preset-react-app@3
更新react项目的默认端口
.env文件
- 项目根目录下新建.env文件
- 写入
PORT=3005
,并重启(注意加入忽略文件)
安装cross-env
- 安装cross-env,注意加 -D
npm i -D cross-env
- 更新启动命令
"start": "cross-env PORT=3006 react-scripts start"
,并重启
更新start.js
- 路径: /node_modules/react-scripts/scripts/start.js
- 更新变量DEFAULT_PORT的默认配置
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3006
;
ref的使用
- class 组件: 使用 ref 属性
- 函数组件:使用 ref属性,
子组件接收ref,使用React.forwardRef((props, ref) => {})
- useImperativeHandle 自定暴露给父组件
- 回调 Refs,将组件实例或 HTML DOM 元素存在state中
何时使用 Refs
管理焦点,文本选择或媒体播放
触发强制动画
集成第三方 DOM 库
一般情况:父子之间使用
父组件
createRef.current
将指向子组件<input/>
const A = () => {
const ref1 = React.createRef();
const ref2 = React.createRef();
const ref3 = React.createRef();
return (
<>
<div
onClick={() => {
console.log(ref1.current);
console.log(ref2.current);
console.log(ref3.current);
}}
>
--
</div>
<B ref={ref1} />
<C ref={ref2} />
<D ref={ref3} />
</>
);
};
// 函数子组件
const B = React.forwardRef((props, ref) => {
return <input ref={ref} />;
});
// class子组件
class C extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 1,
};
}
render() {
return <p>123424</p>;
}
}
// 函数子组件,控制暴露范围
const D = React.forwardRef((props, ref) => {
const [val, setVal] = useState("");
useImperativeHandle(ref, () => ({
val,
}));
return (
<input value={val} ref={ref} onChange={(e) => setVal(e?.target?.value)} />
);
});
特殊情况:爷孙之间使用
父组件
createRef.current
将指向孙子组件<input/>
// 父组件
const A = () => {
const ref2 = React.createRef();
return <>
<div onClick={() =>{console.log(ref2.current.value)}} >--</div>
<C ref={ref2} />
</>
}
const D = (props) => {
return <input ref={props.myRef}/>
}
const C = React.forwardRef((props, ref) => {
return <D myRef={ref}/>
})
回调 Refs
将
element
存入state
const A = () => {
const [ref, setref] = useState(null);
return (
<>
<div
onClick={() => {
ref.value = "23";
ref.focus();
}}
>
input内容更新,并获取焦点
</div>
<input placeholder="请输入" ref={(el) => setref(el)} />
</>
);
};
高阶组件
HOC(高阶组件) 是纯函数,没有副作用。命名
withX
HOC是参数为组件,返回值为新组件的函数。 通过将组件包装在容器组件中来组成新组件
HOC 不需要关心数据的使用方式或原因,而被包装组件也不需要关心数据是怎么来的
静态方法,务必手动复制
ref需要通过React.forwardRef
加入到props中
样例实现了定时刷新数据的功能
import * as React from "react";
import { useState, useEffect } from "react";
const Test01: React.FC = (props) => {
return (
<ul style={{ background: "red", color: "#fff" }}>
{props?.data?.map((item: string) => (
<li key={item}> {item}</li>
))}
</ul>
);
};
const Test02: React.FC = (props) => {
return <div style={{ background: "aqua" }}>{props.data}</div>;
};
function withGetData(WrapperComponent: React.FC | React.Component, fun: Function) {
return class extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
data: null,
};
}
componentDidMount(): void {
setInterval(() => {
let data = fun();
this.setState({
data,
});
}, 1000);
}
render() {
return (
<WrapperComponent
{...this.props}
{...this.state}
/>
);
}
};
}
const Test = () => {
const getData1 = () => {
var result = [];
for (var i = Math.ceil(Math.random() * 10); i > 0; --i)
result.push(getData2());
return result;
};
const getData2 = () => {
const str =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var result = "";
for (var i = Math.ceil(Math.random() * 100); i > 0; --i)
result += str[Math.floor(Math.random() * str.length)];
return result;
};
const Test1 = withGetData(Test01, getData1);
const Test2 = withGetData(Test02, getData2);
return (
<>
<Test1 />
<Test2 />
</>
);
};
export default Test;
Portals
- 可以实现将子节点渲染到非父组件节点
- 父组件可以接受到事件冒泡
import { createPortal } from "react-dom";
// 组件
const PortalTest = () => {
let warppers = document.getElementsByClassName("protal-test");
let el = null;
if (warppers.length > 0) {
el = warppers[0];
} else {
el = document.createElement("div");
el.className = "protal-test";
document.getElementsByClassName("app-root")[0].appendChild(el);
}
return createPortal(
<div
style={{
position: "fixed",
width: 100,
height: "100px",
background: "red",
top: 0,
}}
></div>,
el
);
};
// 使用
<div>
<Test />
</div>
不使用 JSX 的 React 写法
- ·React.createElement(‘div’, null, `Hello ${this.props.toWhat}`);
- react-hyperscript
- hyperscript-helpers
diff算法
- React 引入了 key 属性,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素
不推荐数组中的下标作为 key
Render Props
- 将函数组件放入props中
const A = () => {
return (
<>
<B
child={([x, y]) => (
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xMS41IC0xMC4yMzE3NCAyMyAyMC40NjM0OCI+CiAgPHRpdGxlPlJlYWN0IExvZ288L3RpdGxlPgogIDxjaXJjbGUgY3g9IjAiIGN5PSIwIiByPSIyLjA1IiBmaWxsPSIjNjFkYWZiIi8+CiAgPGcgc3Ryb2tlPSIjNjFkYWZiIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIi8+CiAgICA8ZWxsaXBzZSByeD0iMTEiIHJ5PSI0LjIiIHRyYW5zZm9ybT0icm90YXRlKDYwKSIvPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjApIi8+CiAgPC9nPgo8L3N2Zz4K"
style={{ width: "100px", position: "absolute", left: x, top: y }}
/>
)}
/>
</>
);
};
const B = (props) => {
const [position, setposition] = useState([0, 0]);
const handleMove = ({ clientX, clientY }) => {
setposition([clientX, clientY]);
};
return (
<>
<div style={{ height: "100vh" }} onMouseMove={handleMove}>
移动鼠标
</div>
{props.child(position)}
</>
);
};
静态类型检查
- Flow
- TypeScript
引入方式
- ES6 与 npm 时,
import React from 'react'
- ES5 与 npm 时,
var React = require('react')
高阶组件/API
详见:React 顶层 API
React.memo: 使用场景相同 props
的情况下渲染相同的结果
- 仅检查 props 变更,重新渲染
- 当 state 或 context 发生变化时,重新渲染
- 默认只对对象浅层对比,通过第二个参数(props 相等,返回 true;props 不等,返回 false)传入来实现对比过程控制
//areEqual返回boolean值,props 相等返回 true
export default React.memo(MyComponent, areEqual);
React.Component:并未实现 shouldComponentUpdate;深对比props和state
React.PureComponent浅对比 prop 和 state;实现的shouldComponentUpdate,将跳过所有子组件树的 prop 更新,需要确保所有子组件也都是“纯”的组件
React.Children
map
:this.props.children.map 可能会报错,但React.Children.map不会forEach
: 类似map ,但没有返回count
:一个组件有多少个childrentoArray
:为每个子节点分配一个 keyonly
:只有一个child
const A = (props) => {
console.log(props.children);
console.log(React.Children.count(props.children), 1);
console.log(React.Children.toArray(props.children), 2);
return (
<div>
---
{React.Children.map(props.children, (child) => (child)
)}
</div>
);
};
<A>
<p>qsdwq</p>
<div>wcfevg1</div>
<div>wcfevg2</div>
<div>wcfevg3</div>
{() => <h1>First</h1>}
</A>
生命周期执行顺序
挂载
constructor:在 React 组件挂载之前
getDerivedStateFromProps
render
componentDidMount:在组件挂载后(插入 DOM 树中)立即调用
更新
static getDerivedStateFromProps:
shouldComponentUpdate:更新后会被立即调用,首次渲染不会执行此方法,跳过所有子组件树的 prop 更新
render
static getSnapshotBeforeUpdate:(不常用),则它的返回值将作为 componentDidUpdate的第三个参数 “snapshot
componentDidUpdate
卸载
componentWillUnmount:组件卸载及销毁之前直接调用
错误处理
static getDerivedStateFromError
componentDidCatch
render()
- 纯函数,不会直接与浏览器交互
- 检查 this.props 和 this.state 的变化并返回元素
- 如果 shouldComponentUpdate() 返回 false,则不会调用 render()
constructor()
- 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
- constructor中不要调用 setState() 方法
- 避免将 props 的值复制给 state!
React元素类型
React 元素
数组或 fragments
Portals
字符串或数值类型
布尔类型或 null(test && 的模式,其中 test 为布尔类型)