事件机制,合成事件
是什么
React 基于浏览器的事件机制⾃身实现了⼀套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等。
在 React 中这套事件机制被称之为合成事件
工作过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import React from 'react'; class App extends React.Component{ constructor(props) { super(props); this.parentRef = React.createRef(); this.childRef = React.createRef(); }
componentDidMount() { console.log("React componentDidMount!"); this.parentRef.current?.addEventListener("click", () => { console.log("原⽣事件:⽗元素 DOM 事件监听!"); }); this.childRef.current?.addEventListener("click", () => { console.log("原⽣事件:⼦元素 DOM 事件监听!"); }); document.addEventListener("click", (e) => { console.log("原⽣事件:document DOM 事件监听!"); }); }
parentClickFun = () => { console.log("React 事件:⽗元素事件监听!"); };
childClickFun = () => { console.log("React 事件:⼦元素事件监听!"); };
render() { return ( <div ref={this.parentRef} onClick={this.parentClickFun}> <div ref={this.childRef} onClick={this.childClickFun}> 分析事件执⾏顺序 </div> </div> ); } }
export default App;
|
最终输出
1 2 3 4 5
| 原⽣事件:⼦元素 DOM 事件监听! 原⽣事件:⽗元素 DOM 事件监听! React 事件:⼦元素事件监听! React 事件:⽗元素事件监听! 原⽣事件:document DOM 事件监听!
|
总结
- React 上注册的事件最终会绑定在document这个 DOM 上,⽽不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
- React 通过队列的形式,从触发的组件向⽗组件回溯,然后调⽤他们 JSX 中定义的 callback
- React 有⼀套⾃⼰的合成事件 SyntheticEvent
React中引入CSS
在组件内直接使⽤
内联style引入
组件中引⼊ .css ⽂件
全局生效,样式之间会互相影响
组件中引⼊ .module.css ⽂件
将 css ⽂件作为⼀个模块引⼊,这个模块中的所有 css ,只作⽤于当前组件。不会影响当前组件的后代组件
这种⽅式是 webpack 特⼯的⽅案,只需要配置 webpack 配置⽂件中 modules:true 即可
CSS in JS
CSS-in-JS, 是指⼀种模式,其中 CSS 由 JavaScript ⽣成⽽不是在外部⽂件中定义
此功能并不是 React 的⼀部分,⽽是由第三⽅库提供,例如:
- styled-components
- emotion
- glamorous
本质是通过函数的调⽤,最终创建出⼀个组件:
- 这个组件会被⾃动添加上⼀个不重复的class
- styled-components会给该class添加相关的样式
组件通信
爷孙组件通信
爷孙组件通信主要有 3 种方式:
- 将孙子组件的 props 封装在一个固定字段中
- 通过 children 透传
- 通过 context 传递
假设有个三层组件,爷爷分别给儿子和孙子发红包
先看青铜解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Grandpa() { const [someMoneyForMe] = useState(100); const [someMoneyForDaddy] = useState(101); return <Daddy money={someMoneyForDaddy} moneyForSon={someMoneyForMe} />; } function Daddy(props: { money: number; moneyForSon: number }) { const { money, moneyForSon } = props; return ( <div className="daddy"> <h2>This is Daddy, received ${money}</h2> <Me money={moneyForSon} /> </div> ); } function Me(props: { money: number }) { const { money } = props; return ( <div className="son"> <h3>This is Me, received ${money}</h3> </div> ); }
|
Daddy 组件会透传爷爷给孙子的组件给 Me。这种方案的缺点很明显,以后爷爷要给 Daddy 和 Me 发糖果的时候,Daddy 还得加字段。
将孙子组件的 props 封装在一个固定字段中
按照 1 的方案,我们可以固定给 Daddy 添加一个 sonProps 的字段,然后将 Grandpa 需要传给孙子的状态全部通过 sonProps 传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Grandpa() { const [someMoneyForMe] = useState(100); const [someMoneyForDaddy] = useState(101); return <Daddy money={someMoneyForDaddy} sonProps={{money: someMoneyForMe}} />; } function Daddy(props: { money: number; sonProps: Parameters<typeof Me>[0]; }) { const { money, sonProps } = props; return ( <div className="daddy"> <h2>This is Daddy, received ${money}</h2> <Me {...sonProps}/> </div> ); } function Me(props: { money: number }) { const { money } = props; return ( <div className="son"> <h3>This is Me, received ${money}</h3> </div> ); }
|
这样以后要给 Me 加字段,就不用改 Daddy 了。但要测试 Daddy 时还得 mock Me 组件的数据,Daddy 和 Son 耦合。
通过 children 透传
children 类似于 vue 中的 slot,可以完成一些嵌套组件通信的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function Grandpa() { const [someMoneyForMe] = useState(100); const [someMoneyForDaddy] = useState(101); return ( <Daddy money={someMoneyForDaddy}> <Me money={someMoneyForMe} /> </Daddy> ); } function Daddy(props: { money: number; children?: React.ChildNode }) { const { money, children } = props; return ( <div className="daddy"> <h2>This is Daddy, received ${money}</h2> {children} </div> ); } function Me(props: { money: number }) { const { money } = props; return ( <div className="son"> <h3>This is Me, received ${money}</h3> </div> ); }
|
将 Daddy 的嵌套部分用 children 替代后,解耦了子组件和孙子组件的依赖关系,Daddy 组件更加独立。
作为替代,也可以传递一个组件实例:
三种方案的决策
- 第一种方案一般用于固定结构和跨组件有互相依赖的场景,多见于 UI 框架中的复合组件与原子组件的设计中
- 第二种常用在嵌套层级不深的业务代码中,比如表单场景。优点是顶层 Grandpa 的业务收敛度很高,一眼能看清 UI 结构及状态绑定关系,相当于拍平了 React 组件树
- 第三种比较通用,适合复杂嵌套透传场景。缺点是范式代码较多,且会造成 react dev tools 层级过多;Context 无法在父组件看出依赖关系,必须到子组件文件中才能知道数据来源
高阶组件
本质上是一个函数,接收一个或多个组件作为参数,并返回一个组件
错误边界
错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。
形成错误边界组件的两个条件:
- 使⽤了
static getDerivedStateFromError()
- 使⽤了
componentDidCatch()
抛出错误后,使⽤ static getDerivedStateFromError() 渲染备⽤ UI ,使⽤ componentDidCatch() 打印错误信息,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }
static getDerivedStateFromError(error) { return { hasError: true }; }
componentDidCatch(error, errorInfo) { logErrorToMyService(error, errorInfo); }
render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; }
return this.props.children; } }
|
下⾯这些情况⽆法捕获到异常:
- 事件处理
- 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
- 服务端渲染
- 它自身抛出来的错误(仅能捕获子组件抛出的错误)
setState更新,批量更新
render原理(jsx经过babel编译)、render触发时机
Diff算法
React Portal
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
React router
是什么
react-router 等前端路由的原理⼤致相同,可以实现⽆刷新的条件下切换显示不同的⻚⾯
路由的本质就是⻚⾯的 URL 发⽣改变时,⻚⾯的显示结果可以根据 URL 的变化⽽变化,但是⻚⾯不会刷新因此,可以通过前端路由可以实现单⻚(SPA)应⽤
模式
HashRouter(Hash模式)
- 采用监听
window 上的 hashchange 事件实现;
- path的表现形式是
localhost:3000/#/demo/test;
- 服务器无须做额外配置(因为对于服务器而言,
http://example.com/#/home和http://example.com这两个请求在服务器看来是完全一样的,因为服务器只处理http://example.com这个部分,而#/home这个哈希部分的变化不会触发新的服务器请求)
BrowserRouter(History模式)
BrowserRouter使用的是H5的history API来实现;
- path的表现形式是
localhost:3000/demo/test;
- 服务器需要进行额外的配置(当path变化时,浏览器会向服务器发送请求。服务器需要能够识别这些请求并返回正确的页面或者资源。例如,在服务器重定向配置中,需要设置一个通配符路由(
*)来处理所有可能的路由请求,将请求重定向到应用的入口文件(通常是index.html),这样客户端的 JavaScript 代码才能根据 URL 中的路径来正确地渲染页面)
lazyload
render props、children
Hooks相关
React fiber
React性能优化
React状态管理
Redux
react如何实现ssr