You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import'react-app-polyfill/ie11';import*asReactfrom'react';import*asReactDOMfrom'react-dom';importPopupfrom'../.';// 此处存在parcel alias 见下文import'../dist/react-easy-popup.min.css';// 此处不存在parcel alias 写好相对路径constApp=()=>{return(<div><Popup/></div>);};ReactDOM.render(<App/>,document.getElementById('root'));
进入项目根目录,执行以下命令:
yarn start
现在 src 目录下的内容的变更会被实时监听,在根目录下生成的dist文件夹包含打包后的内容。
开发时调试的文件夹为example,另起一个终端。执行以下命令:
cd example
yarn # 安装依赖
yarn start # 启动example
在localhost:1234可以发现项目启动啦,样式生效且有浏览器前缀。
若 example 启动后网页报错,删除 example 下的.cache 以及 dist 目录重新 start
需要注意的是 example 的入口文件index.tsx引入的是我们打包后的文件,即dist/index.js。
同时还有很重要的一点:portal与普通的 React 子节点行为一致,仍存在于React树,所以Context依旧可以触及。有一些弹层组件会提供xxx.show()的 API 形式进行弹出,这种调用形式较为方便,虽然底层也是基于Portal,但是内部重新执行了ReactDOM.render,脱离了当前主应用的React树,自然也无法获取到Context。
import*asReactfrom'react';import*asReactDOMfrom'react-dom';import{PortalProps}from'./type';constPortal=({ node, children }: PortalProps)=>{returnReactDOM.createPortal(children,node);};exportdefaultPortal;
注意:此处没有使用 React.FC 去进行声明 react-typescript-cheatsheet:Section 2: Getting Started => Function Components => What about React.FC/React.FunctionComponent?
前言
在组件库系列文章中介绍了如何从 0 到 1 搭建一个 React 组件库架子,但为了一两个组件去搭建组件库未免显得大材小用。
这次以移动端一个常用组件
Popup
为例,以最方便快捷的形式发布一个完整的 npm 包。本文包含以下内容:
Popup
组件的开发;README.md
文件。本文不会和组件库那篇文章一般死扣打包细节,因为单个组件和组件库的打包有本质上的区别:
项目初始化
tsdx 内置三种项目模板:
模板还内置了
start
、build
、test
以及lint
等 npm scripts,的确是零配置开箱即用(大误)。为了方便讲解,此处选择
react
模板。执行
npx tsdx create react-easy-popup
,选择react
完成项目创建后进入项目目录。配置 tsdx
由于
tsdx
没有提供样式文件打包支持,使用css in js
方案会带来额外的依赖以及运行时消耗,所以需要简单配置一下tsdx
以支持 less 样式。参照customization-tsdx这一小节进行配置。
安装相关依赖:
新建
tsdx.config.js
,写入以下内容:tsdx.config.js
在
package.json
中配置browserslist
字段。package.json
清空
src
目录,新建index.tsx
、index.less
。src/index.tsx
src/index.less
example/index.tsx
进入项目根目录,执行以下命令:
现在
src
目录下的内容的变更会被实时监听,在根目录下生成的dist
文件夹包含打包后的内容。开发时调试的文件夹为
example
,另起一个终端。执行以下命令:在
localhost:1234
可以发现项目启动啦,样式生效且有浏览器前缀。需要注意的是
example
的入口文件index.tsx
引入的是我们打包后的文件,即dist/index.js
。但是引入路径却为
'../.'
,这是因为tsdx
使用了parcel
的 aliasing。同时,观察根目录下的
dist
文件夹:dist
也可以很轻易地在
package.json
中找到main
、module
以及typings
相关配置。实现 Portal
Popup
在移动端场景下极其常见,其内部基于Portal
实现,自身又可以作为Toast
和Modal
等组件的下层组件。要实现
Popup
,就要先基于ReactDOM.createPortal实现一个Portal
。此处结合官方文档做一个简单总结。
什么是传送门?
Portal
是一种将子节点渲染到存在于父组件以外的DOM
节点的优秀的方案。为什么需要传送门?父组件有
overflow: hidden
或z-index
样式,我们又需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。同时还有很重要的一点:
portal
与普通的React
子节点行为一致,仍存在于React
树,所以Context
依旧可以触及。有一些弹层组件会提供xxx.show()
的 API 形式进行弹出,这种调用形式较为方便,虽然底层也是基于Portal
,但是内部重新执行了ReactDOM.render
,脱离了当前主应用的React
树,自然也无法获取到Context
。清空 src 目录,新建以下文件:
在编写代码之前,需要确定好
Portal
组件的 API。与
ReactDOM.createPortal
方法接受的参数基本一致:指定的挂载节点以及内容。唯一的区别是:Portal
在未传入指定的挂载节点时,会创建一个节点以供使用。在
type.ts
中写入Portal
的Props
类型定义。src/type.ts
现在开始编写代码:
代码实现比较简单,就是调用了一下
ReactDOM.createPortal
,没有考虑到使用者未传入node
的情况:需要内部创建,组件销毁时销毁该node
。同时为了让非 ts 用户能够享受到良好的运行时错误提示,需要安装
prop-types
。src/portal.tsx
这样就完成了
Portal
组件的编写,在入口文件进行导出。src/index.ts
example/index.ts
中引入Portal
,进行测试。example/index.tsx
在网页中看到预期的
DOM
结构。实现 Popup
API 梳理
老规矩,先规划 API,写好类型定义,再动手写代码。
我写这个组件的时候参考了Popup-cube-ui。
最终确定 API 如下:
src/type.ts
编写
Popup
的基本结构。src/popup.tsx
在入口文件进行导出。
src/index.ts
+ export { default as Popup } from './popup';
前置 CSS 知识
在正式开发逻辑之前,先明确一点:
蒙层 Mask 以及内容 Content 入场以及出场均有动画效果。具体表现为:蒙层为 Fade 动画,内容则取决于当前 position,比如内容在中间(position === 'center'),则其动画效果为 Fade,如果在左边(position === 'left'),则其动画效果为 SlideRight,其他 position 以此类推。
再回顾张鑫旭大大的一篇文章:小 tip: transition 与 visibility
划重点:
opacity
的值在0
与1
之间相互过渡(transition
)可以实现 Fade 动画。然而元素即使透明度变成 0,肉眼看不见,在页面上却依旧点击,还是可以覆盖其他元素的,我们希望元素淡出动画结束后,元素可以自动隐藏;display:none
。而display:none
无法应用transition
效果,甚至是破坏作用;visibility:hidden
可以看成visibility:0
;visibility:visible
可以看成visibility:1
。实际上,只要visibility
的值大于0
就是显示的。总结一下:我们想用
opacity
实现淡入淡出的 Fade 动画,但是希望元素淡出后,能够隐藏,而不仅仅是透明度为0
,覆盖在其他元素上。所以需要配置visibility
属性,淡出动画结束时,visibility
值也由visible
变为了hidden
,元素成功隐藏。预设动画样式
借助react-transition-group完成动画效果,需要内置一些动画样式。
新建
animation.less
,写入以下动画样式。展开查看代码
完成基本逻辑
安装相关依赖。
Portal
即可;CSSTransition
组件的in
属性,控制蒙层以及内容的过渡显隐;CSSTransition
组件的unmountOnExit
属性,决定隐藏时是否卸载内容节点;className
;className
,从而控制蒙层有无;用过
antd
的同学都知道,antd
的modal
在首次visible === true
之前,内容节点是不会被挂载的,只有首次visible === true
,内容节点才挂载,而后都是样式上隐藏,而不会去卸载内容节点,除非手动设置destroyOnClose
属性,我们也顺带实现这个特点。展开查看逻辑代码
展开查看样式代码
组件编写完毕,接下来在
example/index.ts
中编写相关示例测试功能即可。example/index.ts
部署 github pages
相信大多数人使用一个 npm 包会先看示例再看文档。
接下来将
example
中的示例项目打包,并部署到 github pages 上。安装
gh-pages
。package.json 新增脚本。
package.json
由于 gh-pages 默认部署在
https://username.github.io/repo
下,而非根路径。为了能够正确引用到静态资源,还需要修改打包的public-url
。修改 example 的 package.json 中的打包命令:
在根目录下执行
yarn deploy
,等脚本执行完再去看看吧。编写 README.md
一份规范的 README 会显得作者很专业,此处使用
readme-md-generator
生成基本框架,向里面填充内容即可。readme-md-generator:📄 CLI that generates beautiful README.md files
README.md
使用 np 发包
在上一篇文章中,专门编写了一个脚本来处理以下六点内容:
生成 CHANGELOG这次就不生成 CHANGELOG 文件了,其他五点配合
np
,操作十分简单。np:A better
npm publish
package.json
--no-yarn
: 不使用yarn
。发包时出现 npm 与 yarn 之间的一些问题;--no-tests
:测试用例暂时还未编写,先跳过;--no-cleanup
:发包时不要重新安装 node_modules;更多配置请查看官方文档。
The text was updated successfully, but these errors were encountered: