React生态技术栈与工程方案
注意事项
注意React每个版本迭代的功能取舍与性能优化问题,并在实际项目中合理运用React的新特性。
React基本概况
React禁止组件之间通过基类继承,而是组件与组件之间通过组合实现代码复用。
React同构解决方案
打造高可靠与高性能的React同构解决方案 https://github.com/alibaba/beidou/blob/master/packages/beidou-docs/articles/high-performance-isomorphic-app.md
- 官网:https://reactjs.org/
- 中文文档:https://react.docschina.org/
- GitHub:https://github.com/facebook/react/
- 已发行版本: https://github.com/facebook/react/releases
菜鸟教程:https://www.runoob.com/react/react-tutorial.html
源码解读:https://react.jokcy.me/ React技术揭秘:https://react.iamkasong.com/ react源码解析:https://xiaochen1024.com/article_item/600ac4384bf83f002edaf54a 社区:https://dev.to/t/react
- React 顶层 API:https://zh-hans.reactjs.org/docs/react-api.html
开源项目列表
后台管理系统:https://github.com/yezihaohao/react-admin 后台管理系统:https://github.com/NLRX-WJC/react-antd-admin-template 后台管理系统:https://github.com/ziyi2/react-tutorial 后台管理系统:https://github.com/z-9527/react-admin-master 模仿美团APP:https://github.com/Yimishine/react-mapp 音乐WebAPP:https://github.com/z-9527/react-music-master QQ音乐WebAPP:https://github.com/ruichengping/react-mobile-qqMusic ghChat IM聊天工具:https://github.com/aermin/ghChat
案例列表
react聊天室|react+redux仿微信聊天IM实例|react仿微信界面:https://www.cnblogs.com/xiaoyan2017/p/11062316.html
基于react的新闻网站:https://github.com/pengxiaohua/news-responsive-by-react
TS声明类型列表
const styleValue: React.CSSProperties = {};
fn: (...args: any[]) => void
React重大更新历史版本
- v16.8.0:添加Hooks。
生命周期
- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM
import React from 'react';
import ReactDOM from 'react-dom/client';
class App extends React.Component {
// 在渲染前调用,在客户端也在服务端 UNSAFE_componentWillMount
componentWillMount() {console.log('Component WILL MOUNT!')}
// 在组件已经被渲染到 DOM 中后运行, 在第一次渲染后调用,只在客户端。
// 之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
componentDidMount() {console.log('Component DID MOUNT!')}
// 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。在此使用newProps而不是this.props,因为this.props可能还是没有改变的旧状态值
componentWillReceiveProps(newProps) {console.log('Component WILL RECEIVE PROPS!')}
// 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。
shouldComponentUpdate(newProps, newState) {return true;}
// 在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
componentWillUpdate(nextProps, nextState) {console.log('Component WILL UPDATE!');}
// 在组件完成更新后立即调用。在初始化时不会被调用。
componentDidUpdate(prevProps, prevState) {console.log('Component DID UPDATE!')}
// 在组件从 DOM 中移除之前立刻被调用。
componentWillUnmount() {console.log('Component WILL UNMOUNT!')}
render() {return null;}
}
const element = (
<React.StrictMode>
<App />>
</React.StrictMode>
);
ReactDOM.createRoot(
document.getElementById('root'),
).render(element);
React API接口
import {
Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, cloneElement, createContext,
createElement, createFactory, createRef, forwardRef, isValidElement, lazy, memo,
startTransition, unstable_act, useCallback, useContext, useDebugValue,
useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
useTransition, version,
} from 'react';
ReactDOM API接口
import {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, createPortal, createRoot,
findDOMNode, flushSync, hydrate, hydrateRoot, render, unmountComponentAtNode,
unstable_batchedUpdates, unstable_renderSubtreeIntoContainer, version,
} from 'react-dom/client';
React属性与方法
import {
Component, // 定义 React 组件的基类
PureComponent, //
memo, //
createElement, //
cloneElement, //
createFactory, //
isValidElement, //
Children, //
Fragment, //
createRef, //
forwardRef, //
lazy, //
Suspense, //
} from 'react';
Component: ƒ Component(props, context, updater)
功能:声明类组件
class Guider extends Component {
render() {
return (
<>
<div></div>
</>
);
}
}
PureComponent: ƒ PureComponent(props, context, updater)
功能:React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。
- [备注]:React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。
memo: ƒ memo(type, compare):React.memo()可接受2个参数,第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()功能类似。
功能:高阶组件,如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。 但只适用于函数组件,而不适用 class 组件。
function Child({seconds}){
console.log('I am rendering');
return (
<div>I am update every {seconds} seconds</div>
)
};
export default React.memo(Child);
Children: {map: ƒ, forEach: ƒ, count: ƒ, toArray: ƒ, only: ƒ}
功能:
Profiler: Symbol(react.profiler)
功能:
StrictMode: Symbol(react.strict_mode)
功能:
lazy: ƒ lazy(ctor)与Suspense: Symbol(react.suspense)
功能:像渲染常规组件一样处理动态引入(的组件) React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。 fallback 属性接受任何在组件加载过程中你想展示的 React 元素。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
cloneElement: ƒ cloneElementWithValidation(element, props, children)
功能:
// 等同于<element.type {...element.props} {...props}>{children}</element.type>
createContext: ƒ createContext(defaultValue, calculateChangedBits)
功能:
createElement: ƒ createElementWithValidation(type, props, children)
功能:
createRef: ƒ createRef()
功能:
class MyComponent extends React.Component {
cmpRef: React.RefObject<HTMLDivElement>;
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus(); // 访问 Refs
}
}
useRef: ƒ useRef(initialValue)
功能:
forwardRef: ƒ forwardRef(render)
功能:React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
isValidElement: ƒ isValidElement(object)
功能:验证对象是否为 React 元素,返回值为 true 或 false。
Fragment: Symbol(react.fragment)
功能:用于减少不必要嵌套的组件,可以使用
<></>
空元素替换<React.Fragment></React.Fragment>
react-dom 属性与方法
import ReactDOM from 'react-dom';
render: ƒ render(element, container, callback)
功能:
ReactDOM.render(element, container[, callback]);
createPortal: ƒ createPortal$$1(children, container)
功能:创建 portal。Portal 将提供一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。 ReactDOM.render直接渲染DOM元素,会直接影响页面
- ReactDOM.createPortal(child, container);
const appRoot = document.getElementById('root');
const modalRoot = document.getElementById('modal-root');
class ModalContainer extends Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}
findDOMNode: ƒ findDOMNode(componentOrElement)
功能:[严格模式下该方法已弃用]findDOMNode 是一个访问底层 DOM 节点的应急方案(escape hatch)。
flushSync: ƒ flushSync(fn, a)
功能:
hydrate: ƒ hydrate(element, container, callback)
功能:[ReactDOMServer服务端渲染]用于在 ReactDOMServer 渲染的容器中对 HTML 的内容进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器。
unmountComponentAtNode: ƒ unmountComponentAtNode(container)
功能:从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。如果组件被移除将会返回 true,如果没有组件可被移除将会返回 false。
ReactDOM.unmountComponentAtNode(container)
unstable_batchedUpdates: ƒ batchedUpdates$1(fn, a)
功能:
unstable_createPortal: ƒ ()
功能:
unstable_renderSubtreeIntoContainer: ƒ unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback)
功能:
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
功能:
Components组件与组合
Components 为可复用组件提供了强大的封装。
重要通知
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
函数组件
function Basic() {
return <button type="button">基础组件一</button>
}
function Parent() {
return <button type="button">基础组件二</button>
}
子组件
<section> <Basic /> </section>
复合组件
<section> <Basic /> <Parent /> </section>
高阶组件(HOC)
高阶组件是参数为组件,返回值为新组件的函数。即将组件转换为另一个组件。 案例:Redux 的 connect 和 Relay 的 createFragmentContainer。 [注意] 不要在 render 方法中使用 HOC
function Basic(WrappedComponent) {
return class extends React.Component {
render() {
// 将 input 组件包装在容器中,而不对其进行修改。Good!
return <WrappedComponent {...this.props} />;
}
}
}
类组件
class Thing extends React.Component {
constructor(props) {
super(props);
this.state = {};
/*
* 组件中的方法遵循与常规 ES6 class 相同的语法规则, 需要在 constructor 中显式地调用 .bind(this) | 备注:使用箭头函数就可以把方法绑定给实例
*
* 方法一:在 constructor 中绑定方法,this.eventHandler = this.eventHandler.bind(this);
* 方法二:使用箭头函数,onClick={ (e) => this.eventHandler(e) }
* 方法三:继续使用 createReactClass
*/
this.clickCount = this.clickCount.bind(this);
}
// 在这里使用箭头函数就可以把方法绑定给实例
handleClick = () => {
console.log(this.state.message);
}
clickCount() {
this.setState({ count: 1 });
}
render() {
return (
<React.Fragment>
<button onClick={ this.clickCount() }>计数</button>
</React.Fragment>
)
}
}
组件通信机制
- 父组件向子组件通信
function Parent() {
return (
<Current message="父组件向子组件通信" />
)
}
function Current({message}) {
return <div>{message}</div>;
}
- 子组件向父组件通信
function Parent() {
const updateHandler = function() {}
return (
<Current updateHandler={updateHandler} />
)
}
function Current({updateHandler}) {
return <div onClick={ () => updateHandler() }></div>;
}
- 跨组件之间互相通信
- 祖组件向孙组件通信
- 孙组件向祖组件通信
组件API
设置状态:setState
语法:setState(object nextState[, function callback])
this.setState(function(state) {
return {name: 'Ysun'};
});
this.setState({
name: 'Ysun'
});
替换状态:replaceState
设置属性:setProps
替换属性:replaceProps
强制更新:forceUpdate
function Thing() {
clickHandler() {
this.forceUpdate();
}
return (
<div onClick={() => this.clickHandler()}>强制更新</div>
)
}
获取DOM节点:findDOMNode
判断组件挂载状态:isMounted
JSX
JSX 是在 JavaScript 内部实现,是一种 JavaScript 的语法扩展。
底层实现原理
原理:通过JavaScript对象的XML形式来描述HTML结构。 React.createElement 实现流程:JSX - Babel编译 + React构造 - JavaScript对象结构 - ReactDOM.render - DOM元素 - HTML页面
表达式
ReactDOM.render(
<div>
<h1>{1+1}</h1>
</div>
,
document.getElementById('example')
);
三元运算
ReactDOM.render(
<div>
<h1>{i == 1 ? 'True!' : 'False'}</h1>
</div>
,
document.getElementById('example')
);
数组
var arr = [
<h1>菜鸟教程</h1>,
<h2>学的不仅是技术,更是梦想!</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
样式
var myStyle = {
fontSize: 100,
color: '#FF0000'
};
ReactDOM.render(
<h1 style = {myStyle}>菜鸟教程</h1>,
document.getElementById('example')
);
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
import
React, {
useState, // 返回一个 state,以及更新 state 的函数。[备注] useState不会自动合并更新对象,可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
useEffect, // 该 Hook 接收一个包含命令式、且可能有副作用代码的函数 | 跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途
useContext, // 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。
useReducer, // useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
useCallback, // 返回一个 memoized 回调函数。
useMemo, // 返回一个 memoized 值。
useRef, // 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。
useImperativeHandle, // 在使用 ref 时自定义暴露给父组件的实例值。
useLayoutEffect, // 它和 useEffect 的结构相同,区别只是调用时机不同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。
useDebugValue, // 在 React 开发者工具中显示自定义 hook 的标签。
useDeferredValue,
useTransition,
useId,
useSyncExternalStore,
useInsertionEffect
} from 'react';
function Example() {
const [age, setAge] = useState(28);
function handleClick() {
setAge(a => a + 1);
}
// 相当于 componentDidMount 和 componentDidUpdate: | 传递依赖列表[]等价于componentDidMount,不传则等价于componentDidUpdate
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
// 等价于componentWillUnmount
return ()=> {
console.log('你走了 Index页面')
}
}, []);
// useCallback(fn, deps)
// 把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useEffect(() => {
memoizedCallback();
}, [memoizedCallback]);
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
Content
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
注意事项
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。
API接口
- React.createContext:创建一个 Context 对象,React.createContext(defaultValue);
- Context.Provider:声明供应组件传递 context 值
- Class.contextType:在类中读取
- Context.Consumer:声明消费组件订阅 context 变化
- Context.displayName:React DevTools 使用该字符串来确定 context 要显示的内容
const rootContext = React.createContext('light');
rootContext.displayName = 'MyDisplayName';
const contentData = { title: "Context机制", };
class Ancestry extends React.Component {
render() {
return (
<rootContext.Provider value={contentData}>
<Parent />
</rootContext.Provider>
);
}
}
function Parent() {
return (
<div>
<Current />
</div>
);
}
class Current extends React.Component {
static contextType = rootContext;
render() {
// 引用数据
console.log('Content:', this.context);
return (
<rootContext.Consumer>
{content => (
<p>{content.title}</p>
)}
</rootContext.Consumer>
);
}
}
React核心机制
注意事项
React18版本后不再支持ReactDOM.render(),需改用ReactDOM.createRoot(rootContainer).render(element);
State更新
对象写法:同步更新
this.setState({
student: {
count: 0,
address: '广东省深圳市南山区',
}
});
函数式写法:异步更新,依赖他们的值来更新下一个状态,这个函数相当于使用生命中期中的ComponentDidupdate函数的作用
this.setState((state, props) => ({
counter: state.counter + props.step
}));
严格模式
StrictMode 是一个用来突出显示应用程序中潜在问题的工具,为其后代元素触发额外的检查和警告。
注意事项
严格模式检查仅在开发模式下运行;它们不会影响生产构建。
// 不会对 Header 和 Footer 组件运行严格模式检查。
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
- StrictMode积极作用
- 识别不安全的生命周期
- 关于使用过时字符串 ref API 的警告
- 关于使用废弃的 findDOMNode 方法的警告
- 检测意外的副作用
- 检测过时的 context API
React.Suspense组件
React.Suspense 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<OtherComponent />
</React.Suspense>
);
}
ReactDOM.createPortal
创建 portal。Portal 将提供一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。
const appRoot = document.getElementById('root');
const modalRoot = document.getElementById('modal-root');
class ModalContainer extends Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}
Redux
Redux 由 Flux 演变而来,Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
Reducer理念的历史渊源
Array.reduce()对数组的处理是一次处理数组中的每一项并返回一个最终结果。而Reducer正是基于Array.reduce()而实现的一种处理数据机制。
Redux API接口
import {
__DO_NOT_USE__ActionTypes,
applyMiddleware,
bindActionCreators,
combineReducers,
compose,
createStore,
legacy_createStore,
} from 'redux';
React-Redux
react-redux是react官方推出的redux绑定库。
API接口
import {
Provider,
ReactReduxContext,
connect,
createDispatchHook,
createSelectorHook,
createStoreHook,
shallowEqual,
useDispatch,
useSelector,
useStore,
} from 'react-redux';
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
[mapStateToProps(state, [ownProps]): stateProps] (Function): // 如果您的mapStateToProps函数声明为带有两个参数,则它将以存储状态作为第一个参数,并将传递给连接组件的props作为第二个参数来调用,并且只要连接的组件收到新的参数,就会重新调用通过浅等式比较确定的道具。(根据惯例,第二个参数通常称为ownProps。)
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): [mergeProps(stateProps, dispatchProps, ownProps): props] (Function): [options] (Object)
代码示例
组件:static\src\components\stage\page\page.tsx
class Basic extends React.Component {
render() {
const { } = this.props;
return (
<div></div>
);
}
}
static\src\containers\stage\page.tsx
import React from 'react';
import { connect } from 'react-redux';
import Basic from 'components/stage/baisc';
const mapStateToProps = (state, ownProps) => {
console.log(ownProps); // { id: '', editable: true }
return state;
};
//添加dispatch方法
const mapDispatchToProps = (dispatch) => {
return { dispatch };
}
export default connect(mapStateToProps, mapDispatchToProps)(Basic);
static\src\components\stage\canvas\canvas.tsx
import Page from 'containers/stage/page';
function Canvas(props: CanvasProps, ref: React.Ref<HTMLDivElement>) {
return (
<Page id={pageId} editable={true} />
);
}
export default React.forwardRef(Canvas);
TS Redux
// 语法一
function mapDispatchToProps(dispatch: Dispatch) {
return {
onInitCurtainActivedIndex(index: number) {
const dispatchHandler = () => {
return async (dispatch: Dispatch<any>, getState: () => StoreState) => {
console.log('\n〔日志〕:', index, '\n\n 〔日志〕:', getState(), '\n\n');
};
};
dispatch<any>(dispatchHandler());
// 更新当前幕布序列号与同步幕布尺寸
// dispatch(
// updateCmp(pipCmp.id, {
// activedIndex: index,
// })
// );
},
};
}
// 调度
componentDidMount() {
const { onInitCurtainActivedIndex } = this.props;
onInitCurtainActivedIndex && onInitCurtainActivedIndex(0);
}
// 案例一:业务
interactControl(interactType: number) {
const { onUpdatePipKey } = this.props;
onUpdatePipKey({ interactType });
}
// static\src\containers\settings\picture-in-picture.tsx
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
onUpdatePipKey(data: Record<string, any> = {}) {
dispatch<any>(updatePipKey(data));
},
onUpdateCmp(cmpId: string, data: Record<string, any> = {}) {
dispatch<any>(updateCmp(cmpId, data));
},
};
};
export default connect( mapStateToProps, mapDispatchToProps)(PipSettings);
// static\src\actions\picture-in-picture.ts
export function updatePipKey(data: Record<string, any>) {
return (dispatch: Dispatch<any>, getState: () => StoreState) => {
const { pages, cmps } = getState();
dispatch(updateCmp(pipCmp.id, {...data,}));
}
}
// 案例二:[声明]
export function addFont(data: CmpModel.FontMaterial) {
return function(dispatch: Dispatch<any>, getState: () => StoreState) {
dispatch(updateCmp(cmp.id, { src: defaultSrc, imageMaterials: '', style: defaultStyle }));
}
}
components/redux-example.tsx
Redux |
<ReduxExample />
import React, { Component } from 'react';
interface IProps {}
interface IState {}
class ReduxExample extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {};
}
render() {
return (
<>
<p>11</p>
</>
);
}
}
export default ReduxExample;
container/redux-example.tsx
import { connect } from 'react-redux';
import ReduxExample from 'components/redux-example.tsx';
function mapStateToProps(state: StoreState) {
const { cmps, stage } = state;
return {}
}
function mapDispatchToProps(dispatch: Dispatch) {
return {};
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ReduxExample);
异步获取数据
export async function getAppData(appId: string) {
const url = `/api/editor/app/${appId}`;
const appData: AppModel = await get(url, null, {
timeout: INIT_TIMEOUT,
}).catch(handleTimeout);
// 拍扁原始结构化的作品数据
return await flatten(appData);
}
export function fetchAppData(appId: string, callback?: (data: FlattenData) => void) {
return async (dispatch: Dispatch<any>) => {
try {
const appData = await appApi.getAppData(appId);
const newAppData = await updatePluginVersion(appData);
dispatch(initAppData(newAppData));
await dispatch(getConfigToPlugin(appId));
callback && callback(newAppData);
} catch (e) {
const err = e as FetchError;
dispatchError({ dispatch, err });
}
};
}
// SSO初始化成功(自动登录成功),拉取作品数据
dispatch(initAppData(query.appid, appActions.fetchAppData, userInfo, query.pinstance));
// 根据不同的参数和接口,初始化编辑器作品数据
export function initAppData(
id: string,
api: (id: string, cb?: (data: FlattenData) => void) => (dispatch: Dispatch<any>) => Promise<any>,
userInfo?: UserModel,
pluginInstanceId?: string
) {
return (dispatch: Dispatch<any>) => {
dispatch(
api(id, async data => {
// 拉完作品数据,自动保存一次
if (userInfo && userInfo.isLoggedIn) {
await dispatch(appActions.saveAppData({ isFirstAutosave: true }));
// 获取购车中应有的图片素材列表
dispatch(checkCartImageMaterials());
}
Loading.hide();
// 获取购物车中应有的字体素材列表
dispatch(getUserFontMaterial(() => dispatch(checkCartFontMaterials())));
// 获取购物车中 正版音乐
dispatch(checkCartMusicMaterials());
// 检测作品中的付费音频
dispatch(checkAudioCopyright());
// 开放平台过滤 组件
await dispatch(filterPayCmp(data));
const { byId = {} } = data.cmps;
// 判断是否有插件数据,若有插件数据,打开插件素材库弹窗
const cmps = Object.values(byId);
const plugins = cmps.filter(o => CmpModelClass.isPlugin(o));
if (plugins.length > 0) {
// 没有插件权限直接跳过后续步骤
if (userInfo && !userInfo.openEditorPlugins) {
dispatch(setSaveActionDisabledStatus(true));
return;
}
// 检查玩法状态
checkPluginsStatus();
dispatch(showPluginMaterial());
dispatch(showPluginManager(true));
}
// 如果有插件实例id,找到对应页面并打开配置
if (pluginInstanceId) {
const activePlugin = getActivePlugin(pluginInstanceId);
if (activePlugin) {
dispatch(activePage(activePlugin.pid));
dispatch(activeCmp(activePlugin.id));
dispatch(showPluginModal(activePlugin));
}
}
initPosterEditor(dispatch);
})
);
};
}
redux-thunk
Redux 的Thunk中间件,它允许编写内部带有逻辑的函数,这些函数可以与 Redux 存储dispatch和getState方法进行交互。
- Redux Thunk:https://github.com/reduxjs/redux-thunk
注册Store
- store.ts
import {
createStore,
combineReducers,
applyMiddleware,
compose,
StoreEnhancerStoreCreator,
} from 'redux';
import * as reducers from 'reducers';
import thunk from 'redux-thunk';
const _reducers = combineReducers<StoreState>({ ...reducers });
// 开启chrome redux-dev-tool调试
const extension = (window as any).__REDUX_DEVTOOLS_EXTENSION__;
const devTool = extension
? extension({
trace: true,
traceLimit: 25,
})
: (f: any) => f;
const enhancer = compose<StoreEnhancerStoreCreator<StoreState>>(
applyMiddleware(thunk),
devTool
);
export default createStore(_reducers, enhancer);
- index.tsx
import Editor from '../editor';
import store from 'store';
ReactDOM.render(
<Provider store={store}> <Editor /> </Provider>,
document.getElementById('editor')
);
容器组件
容器组件,负责处理数据,并将处理完整的数据结果返回给业务组件。
import { Dispatch } from 'redux';
import { connect, useSelector, suseStore, useDispatch } from 'react-redux';
import Service from 'components/Service';
function mapStateToProps(state, ownProps) {
const { name } = state;
const { id } = ownProps;
return {
name: '',
id,
}
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
add: () => {
const dispatchHandler = () => {
return async (dispatch: Dispatch<any>, getState: () => StoreState) => {
const state = getState();
dispatch(Fn());
};
};
dispatch<any>(dispatchHandler());
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Service);
业务组件
专注于实现业务逻辑,其中数据由容器组件提供。
export default class Service extends Component<Props, State> {
render() {
const { name } = this.props;
return null;
}
}
Recoil状态管理库
Recoil 是 React 的状态管理库。
- 官网:https://recoiljs.org/zh-hans/
- GitHub:https://github.com/facebookexperimental/Recoil
- 安装:pnpm install recoil
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<App />
</RecoilRoot>
);
}
class App extends React.Component {
//
}
react-router
React Router 是用于React JavaScript 库的轻量级、功能齐全的路由库。 在React路由中,有两个库可以实现路由功能:react-router、react-router-dom
react-router不能通过操作dom控制路由
English:https://reacttraining.com/react-router/web/guides/quick-start
API 接口:http://react-guide.github.io/react-router-cn/docs/API.html
https://github.com/remix-run/react-router GitHub:https://github.com/ReactTraining/react-router
API技术文档:https://github.com/ReactTraining/react-router/tree/master/packages/react-router-dom/docs/api
react-router API接口
import {
MemoryRouter, Navigate, Outlet, Route, Router, Routes, UNSAFE_LocationContext,
UNSAFE_NavigationContext, UNSAFE_RouteContext, createRoutesFromChildren,
generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref,
useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType,
useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes,
} from 'react-router';
import {
Router,
Route,
Switch,
withRouter,
matchPath,
generatePath,
StaticRouter
} from "react-router";
this.props.history.push('/path'); // 导航跳转
react-router-dom
react-router-dom是基于react-router,加入了在浏览器运行环境下的一些路由功能。
react-router-dom API接口
import {
BrowserRouter, HashRouter, Link, NavLink, createSearchParams,
unstable_HistoryRouter, useLinkClickHandler, useSearchParams,
} from 'react-router';
代码示例
- router/index.js
import React from "react";
import {
BrowserRouter as Router,
Switch,
Routes,
Route,
Link
} from "react-router-dom";
import PageHome from '../views/Home';
class RouterConfig extends React.Component {
render() {
return (
<BrowserRouter>
<Routes>
<Route path='/' element={ <PageHome /> } />
<Route path='/react' element={ <PageReact /> } />
<Route path='/redux' element={ <PageRedux /> } />
<Route path='/redux-router' element={ <PageReduxRouter /> } />
<Route path='/redux-router-dom' element={ <PageReduxRouterDom /> } />
<Route path='/sass' element={ <PageSass /> } />
<Route path='/less' element={ <PageLess /> } />
<Route path='/axios' element={ <PageAxios /> } />
<Route path='/element-ui' element={ <PageElementUI /> } />
<Route path='/ant-design' element={ <PageAntDesign /> } />
<Route path='/login' element={ <PageLogin /> } />
<Route path='/about' element={ <PageAbout /> } />
</Routes>
</BrowserRouter>
)
}
}
export default RouterConfig;
- index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import RouterConfig from './router/index.js';
import store from './store/index.js';
const element = (
<React.StrictMode>
<Provider store={store}>
<RouterConfig />
</Provider>
</React.StrictMode>
);
ReactDOM.createRoot(
document.getElementById('root'),
).render(element);
react-router-dom
react-router-dom在版本6以上改变了很多语法,尤其是:component=改为element=
- English:https://reacttraining.com/react-router/web/guides/quick-start
基础功能
添加路由:this.props.history.push('/path')
替换路由:this.props.history.replace(/home/about
)
前进路由:this.props.history.goForward()
后退路由:this.props.history.goBack()
编程式导航
具体示例
import React from "react";
import {
BrowserRouter as Router,
HashRouter,
HashHistory,
Switch,
Router,
Route,
Link,
NavLink,
useParams,
useRouteMatch,
Redirect,
useHistory,
useLocation,
useRouteMatch
} from "react-router-dom";
import LoginPage from './views/account/login.js';
export default function Basic() {
return (
<Router>
<Link to="/login" element={ LoginPage }>登录</Link>
<Switch>
<Route path='/' exact render={()=>(
<Redirect to='/Page1'/>
)}/>
<Route exact path="/">
<Home />
</Route>
<Route path="/:id" children={<Child />} />
</Switch>
</Router>
);
}
function Child() {
let { id } = useParams();
let { path, url } = useRouteMatch();
return (
<div>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
<h3>ID: {id}</h3>
</div>
);
}
function Topic() {
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
BrowserRouter.js:定义HTML5 History 模式
HashRouter.js:定义Hash模式
import {
BrowserRouter as Router,
} from 'react-router-dom';
import { createHashHistory } from "history";
const history = createHashHistory();
<Router history={history}>
</Router>
Link.js
添加路由链接,该标签必须存放在BrowserRouter或HashRouter内的组件中,否则会报错。
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
replace
to="/home"
>路由链接</Link>
NavLink.js
添加路由链接,NavLink有个active的状态类名,可以通过activeClassName来设置其样式
<NavLink
to="/about"
className={} | ""
activeClassName={} | ""
style={} | ""
activeStyle={} | ""
>路由链接</NavLink>
MemoryRouter.js
Prompt.js
Switch.js
该组件在版本6以上被
<Routes>
替代。 如果有多个组件根据点击相应的路由展示,就需要用到Switch组件进行判断,即仅仅展示一个路由组件,否则将展示全部组件
<Switch>
<Route path='/home/message' element={ <Message /> } />
<Redirect to='/home/news' element={ <News /> } />
</Switch>
Redirect.js
重定向
<Redirect to='/home/news' element={ <News /> } />
Route.js
如果只有一个组件要展示直接写一个
<Route>
标签进行展示即可,Route需要设置两个属性,path和component,path是设置路由地址,component是相对应要展示的组件
<Route path='/home/news' element={News}></Route>
路由传参, 参数写在冒号后面,参数名可以自己定义,参数值可以在props.match.params中找到
<Route path='/home/message/messagedetail/:id' element={MsgDetail} />
Router.js
StaticRouter.js
generatePath.js
matchPath.js
withRouter.js
案例
<HashRouter>
<App>
<Switch>
<Route path="/login" element={ <Login /> }/>
<Route path="/common" render={() =>
<Common>
<Route path="/common/order/detail/:orderId" element={ <OrderDetail /> } />
</Common>
} />
<Route path="/" render={()=>
<Admin>
<Switch>
<Route path='/home' element={ <Home /> } />
<Route path="/ui/buttons" element={ <Buttons /> } />
<Route path="/ui/modals" element={ <Modals />} />
<Redirect to="/home" />
</Switch>
</Admin>
} />
</Switch>
</App>
</HashRouter>
Next.js与服务端渲染 (SSR)
Next.js 为您提供生产环境所需的所有功能以及最佳的开发体验:包括静态及服务器端融合渲染、 支持 TypeScript、智能化打包、 路由预取等功能 无需任何配置。
ReactDOMServer:https://zh-hans.reactjs.org/docs/react-dom-server.html
安装配置
> 安装:npx create-next-app@latest [nextapp]
> 本地构建:pnpm run dev
> 线上打包:pnpm run build
> PM2部署:pm2 start npm --name [nextapp] -- run build
- next.config.mjs
const nextConfig = {
distDir: 'build',
}
export default nextConfig;
动态路由
将括号添加到页面 ( [param]) 以创建动态路由(也称为 url slugs、pretty urls 等)。
// pages/detail/[pid].js
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
- 捕捉所有路线
...
通过在括号内添加三个点 () 可以扩展动态路由以捕获所有路径。例如pages/detail/[...param].js
- 可选捕获所有路线
[[...slug]]
通过将参数包含在双括号 ( )中,可以使捕获所有路由成为可选的。
获取数据
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
return {
props: { ...data }, // will be passed to the page component as props
}
}
Preact
Preact,具有相同现代 API 的 React 的快速 3kB 替代品。
- 官网:https://preactjs.com/
React Native
React Native 将原生开发的最佳部分与 React 相结合, 致力于成为构建用户界面的顶尖 JavaScript 框架。
中文网:https://www.react-native.cn/ 社区生态系统:https://native.directory/ 组件库:https://www.react-native.cn/docs/components-and-apis
脚手架工具集成方案
create-react-app
React团队官方出的一个构建React单页面应用的脚手架工具
- 中文文档:https://www.html.cn/create-react-app/
- GitHub:https://github.com/facebook/create-react-app
安装配置
> npm install -g create-react-app
# 构建项目
> npx create-react-app [project_name]
> cd [project_name]
> npm start
# 构建生产版本
> npm run build
- 异常问题
# 失败,npm无法拉取国外资源
# 备注:如果npx create-react-app失败,可以采取以下途径构建项目
> cnpm init react-app [project_name]
# 如果失败, 见/document/download/npm.md文档配置
CSS样式冲突解决方案 | 注意webpack配置less或sass的问题,因为配置错误就导致报错。
// index.module.css
.layout {}
import Styles from './index.module.css';
<div className={ Styles['layout'] }></div>
配置TypeScript
> npx create-react-app [project_name] --typescript
配置LESS
- create-react-app 脚手架中并没有配置关于 less 文件
- 注意报错TypeError: this.getOptions is not a function,因为8.0+版本过高导致,因为高版本的配置改变了。所以需要降低版本,或者根据高版本的配置要求来更新配置。
npm run eject; # 此命令会将脚手架中隐藏的配置都展示出来,此过程不可逆
# 直接拷贝sass配置,然后替换成less即可
# 添加LESS解析规则 | 打开config目录下文件webpack.config.js,并在// style files regexes 注释位置后面添加如下两句代码
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
# 找到 rules 属性配置,在其中添加 less 解析配置 | 这些 less 配置规则放在 sass 的解析规则下面即可,如果放在了 file-loader 的解析规则下面,less 文件解析不会生效。
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
},
'less-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
},
# 配置完成,安装 less 和 less-loader 插件 | yarn add less less-loader
cnpm install less less-loader --save;
配置SASS
create-react-app 脚手架中已经添加了 sass-loader 的支持,所以只需要安装 node-sass 插件即可。
npm install node-sass --save;
import './index.sass';
# 可能出现问题:Node Sass version 6.0.1 is incompatible with ^4.0.0 || ^5.0.0.
npm uninstall node-sass;
npm install node-sass@5.0.0 --save;
# 重启项目
yarn start
工程化建设方案
React工程化配置,即采用现代开发构建部署规范工具实现团队协作的高效率高质量开发流程标准规范。
安装配置typescript
ESLint
PWA部署
LESS
实现LESS作用域的约束
- index.module.less
.less-layout {
p {
font-size: 24px;
color: #f00;
}
}
- index.tsx
import { Fragment } from 'react';
import Styles from './index.module.less';
export default function less() {
return (
<Fragment>
<p>LESS</p>
<div className={ Styles['less-layout'] }>
<p>LESS</p>
</div>
</Fragment>
);
}
SASS
实现SASS作用域的约束
- index.module.sass
.sass-layout {
p {
font-size: 24px;
color: #f00;
}
}
- index.tsx
import { Fragment } from 'react';
import Styles from './index.module.sass';
export default function sass() {
return (
<Fragment>
<p>SASS</p>
<div className={ Styles['sass-style'] }>
<p>示例</p>
</div>
</Fragment>
);
}
单元测试Jest
PWA
Commitlint
性能调优最佳实践
列表中key值优化
- https://reactjs.org/docs/lists-and-keys.html#keys
虚拟化长列表
如果你的应用渲染了长列表(上百甚至上千的数据),我们推荐使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地降低重新渲染组件消耗的时间,以及创建 DOM 节点的数量。
- react-window:https://github.com/bvaughn/react-window
- react-virtualized:https://github.com/bvaughn/react-virtualized
immutable(不可变)
避免由于引用或继承等原因出现其对象的变化而导致变化的问题。
// 扩展符
{ ...object }
// Object.assign({}, object);
充分运用组件缓存机制
- React.memo
- useCallback
- shouldComponentUpdate
shouldComponentUpdate(nextProp, nextState) {
return !isPropsStateEqual(nextProps, this.props);
}
React生态系统
Ant Design
antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
import { Button, DatePicker } from 'antd';
const App = () => (
<>
<Button type="primary">PRESS ME</Button>
<DatePicker placeholder="select date" />
</>
);
react-hook-form
用于表单状态管理和验证的 React Hooks。
import React from 'react';
import { useForm } from 'react-hook-form';
function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('firstName')} />
<input {...register('lastName', { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<input {...register('age', { pattern: /\d+/ })} />
{errors.age && <p>Please enter number for age.</p>}
<input type="submit" />
</form>
);
}
React Query
用于在 React 中获取、缓存和更新异步数据的钩子。
CountUp.js
CountUp.js 是一个无依赖的轻量级 JavaScript 类,可用于快速创建以更有趣的方式显示数字数据的动画。
- 官网:https://inorganik.github.io/countUp.js/
- react-countup:https://github.com/glennreyes/react-countup
- GitHub:https://github.com/inorganik/CountUp.js
- 安装配置
> yarn add react-countup
react-devtools
- NPM:https://www.npmjs.com/package/react-devtools
redux-devtools
- GitHub:https://github.com/reduxjs/redux-devtools
- Git:https://www.npmjs.com/package/redux-devtools
import {
createStore,
combineReducers,
applyMiddleware,
compose,
StoreEnhancerStoreCreator,
} from 'redux';
import * as reducers from 'reducers';
import thunk from 'redux-thunk';
const _reducers = combineReducers<StoreState>({ ...reducers });
// 开启chrome redux-dev-tool调试
const extension = (window as any).__REDUX_DEVTOOLS_EXTENSION__;
const devTool = extension ? extension() : (f: any) => f;
const enhancer = compose<StoreEnhancerStoreCreator<StoreState>>(
applyMiddleware(thunk),
devTool
);
export default createStore(_reducers, enhancer);
reactweb-cli
淘宝前端团队开发的一个可以把react-native转换成web的工具, 大体上能实现了移动端的iOS/安卓/移动web这三端的代码共用
create-react-class
const createReactClass = require('create-react-class');
var Thing = createReactClass({
// defaultProps 属性
getDefaultProps: function() {
return {
name: 'thing'
}
}
// 初始化 State
getInitialState: function() {
return {
name: 'thing'
}
}
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
reactScrollbar
- https://github.com/souhe/reactScrollbar