React
整体刷新
组件+单项数据流
React组件
1.React组件一般k提供方法f而是某种状态机
2.React组件可以理解q一o纯函数
3.单向数据绑定
受控组件 vs 非受控组件
- 受控组件:表单元素状态由使用者维护
- 非受控组件:表单元素状态DOM自身维护
理解 JSX:是模板语言只是一种语法糖
jsx:在js代码中直接写HTML标记,其本质:动态创建组建的语法糖React.createElement
。
- 在jsx中使用表达式
- jsx 本身也是表达式
const e=<h1>eee</h1>;
3. 在属性中使用表达式
<View foo={1+2+3} />;
5. 扩展属性
const obj={a:'222',c:1111}
const e=<View {...obj} />;
6. 表达式作为子元素
const e=<h1>{props.msg}</h1>;
约定:自定义组件以大写字母开头
1.React 认q小写的 tag 是原生 DOM 节点,如 div
2.大写字母开头为自定义组件
3.JSX标记可以直接使用属性语法,例如<menu.Item />,
React 组件的生命周期及其使用场景
constructor
- 用于初始化 内部状态,很少使用
- 唯一可以直接修改state的地方
getDerivedStateFromProps()
- 当state需要从props初始化时使用
- 尽量不要使用:维护两者状态一致性会增加复杂度
- 每次render都会调用
- 典型场景:表单控件获取默认值
componentDidMount()
- UI渲染完成后调用
- 只执行一次
- 典型场景:获取外部资源
componentWillUnmount()
- 组件移除时被调用
- 典型场景:资源释放
getSnapshotBeforeUpdate()
- 在页面render之前调用,state已更新
- 典型场景:获取render之前的DOM状态
componentDidUpdate()
- 每次UI更新时被调用
- 典型场景:页面需要根据props变化重新获取数据
shouldComponentUpdate
- 决定Virtual DOM是否重绘
- 一般可以由PureComponent自动实现
- 典型场景:性能优化
Virtual DOM原理和Key的属性作用
如何工作的,没有进行优化前的复杂度是:O(n^3)
- 广度优先的分层比较
- 节点类型变化,直接对比节点,如果多出 直接append,或者删除节点。
- 节点跨层移动:直接对比,如果和以前进行对比 ,不存在就直接删除,如果多出 就直接创建一个新的节点,不会跨层移动节点。
线上演示
虚拟DOM的两个假设
- 组件的DOM结构是相对稳定的(很少出现跨层移动的场景)
- 类型相同的兄弟节点可以被唯一标识(节点的位置发生变化的时候,这里就是那个key)
- 组件的DOM结构是相对稳定的
- 类型相同的兄弟节点可以被唯一标识
- 算法复杂度为 O(n)
组件复用的另外2种形式:高阶组件和函数作为子组件
高阶组件(HOC)
高阶组件接受组件作为参数,返回新的组件。
函数作为子组件
// value 是 上面传过来的name;children是 返回的整个函数
{this.props.value && this.props.children(this.props.value)}
理解 Context API 的使用场景
函数作为子组件的设计模式
- 使用
React.createContext
创建一个新的context
const LocaleContext = React.createContext(enStrings);
- 提供
Provider
组件
<LocaleContext.Provider value={this.state.locale}>
<button onClick={this.toggleLocale}>
切换语言
</button>
{this.props.children}
</LocaleContext.Provider>
- 在
Provider
组件下,使用Consumer
组件;否则的话Consumer
组件 就使用的默认初始值
<LocaleContext.Consumer>
{locale => (
<div>
<button>{locale.cancel}</button>
<button>{locale.submit}</button>
</div>
)}
</LocaleContext.Consumer>
以下是全部代码
import React from "react";
const enStrings = {
submit: "Submit",
cancel: "Cancel"
};
const cnStrings = {
submit: "提交",
cancel: "取消"
};
const LocaleContext = React.createContext(enStrings);
class LocaleProvider extends React.Component {
state = { locale: cnStrings };
toggleLocale = () => {
const locale =
this.state.locale === enStrings
? cnStrings
: enStrings;
this.setState({ locale });
};
render() {
return (
<LocaleContext.Provider value={this.state.locale}>
<button onClick={this.toggleLocale}>
切换语言
</button>
{this.props.children}
</LocaleContext.Provider>
);
}
}
class LocaledButtons extends React.Component {
render() {
return (
<LocaleContext.Consumer>
{locale => (
<div>
<button>{locale.cancel}</button>
<button>{locale.submit}</button>
</div>
)}
</LocaleContext.Consumer>
);
}
}
export default () => (
<div>
<LocaleProvider>
<div>
<br />
<LocaledButtons />
</div>
</LocaleProvider>
<LocaledButtons />
</div>
);
Redux状态管理框架
- 单项数据流
- 唯一状态来源
- 纯函数更新store,通过action触发
Store,action,reducer
Redux异步请求
这里多了一个Middlewares中间件,这里举个例子,使用redux-thunk
;它接收到actions的时候,不是直接访问dispatch,而是去访问api,如果请求的api返回的是一个成功,他就会返回一个成功的action,失败的话 就会返回一个失败的action。也就是Middlewares进行先截获,预处理,再返回新的action。异步action是几个同步action的使用,通过中间件进行实现的。
Redux运行基础,不可变数据
- 性能优化(产生一个新的数据对象,只比较数据引用的地址是否相等,更换新的数据对象)
- 易于调试和跟踪(记录了前一个状态和后一个状态)
- 易于推测(action 推测 前后的状态)
操作不可变数据
- {…},Object.assign
- immutability-helper //深层次的节点
- immer // 建立一个代理,但是数据比较多的时候 性能会不太好
React Router 路由
特性
- 声明式路由定义
<Link to="/home">Home</Link>
- 动态路由
<Route path="/home" component={Home} />
实现方式
- URL路径
BrowserRouter
- hash路由
HashRouter
- 内存路由
MemoryRouter
(浏览器URL不发生变化,路由存放到内存中)
API
1.<Link>: 普通链接,不会触发浏览器刷新
2.<NavLink>:类似 Link 但是会添加当前选中状态
3.<Prompt> :满足条件时提示用户是否离开当前页面
4.<Redirect>: 重定向当前页面,例如登录判断
5.<Route> : 路由配置的核心标记f路径匹配时显示对应组件
6. <Switch>:只显示第一个匹配的路由
URL传递参数
<Route path="/topic/:id" .../>
this.props.match.params
嵌套路由
- 每个React组件都可以是路由容器
- React Router 的声明式语法可以方便的定义嵌套路由
UI组件库
- Antd Design (比较齐全)
- Material UI (没有tree)
- Semantic UI (没有tree)
同构应用
简单介绍
使用 next.js
开发。
https://www.nextjs.cn/
- 页面就是 pages 目录下的一个组件
- static 目录映射静态文件
- page具有特殊静态方法 getInitialProps
- 动态加载资源 dynamic
单元测试
一些工具
1.Jest:Facebook开源的 js单元测试框架
2.JS DOM 浏览器环境的 NodeJS 模拟
3.Enzyme:React 组件渲染和测试
4.nock模拟 http 请求
8. sinon: 函数模拟和调用跟踪
6.istanbul:单元测试覆盖率
常用开发调试工具
- ESLint (告诉你发生的错误)
- Prettier (保存自动格式化)
- React DevTools
- Redux DevTools
架构
前端项目的理想架构:
可维护,可扩展,可测试,易开发,易构建
易于开发
- 开发工具是否完善
- 生态圈是否繁荣
- 社区是否活跃
易于扩展
- 增加新功能是否容易
- 新功能是否显著增加系统复杂度
易于维护
1.代码是否容易理解
3. 文档是否健全
易于测试
4. 功能的分层是否清晰
5. 副作用少
6. 尽量使用纯函数
易于构建
7. 使用通用技术和架构
8. 构建工具的选择
文件夹结构
- 按feature组织源文件
- 组件和样式文件同一级
- Redux单独文件夹
- 单元测试保持同样目录结构放在 tests 文件夹
index.js
内的数据;
- 每个feature 都有自己的专属路由配置
- 顶层路由使用JSON配置 更易维护和理解
- 如何解析JSON配置到React Router 语法
页面简单逻辑
列表页面 请求
处理多个数据请求的时候
1.请求之间无依赖 关系,可以并发进行
2.请求有依赖 需要依次进行
3.请求完成之前,页面显示 Loading 状态
向导页面需要考虑的技术要点
1.使用 URL 进行导航的好处
2.表单内容存放的位置
3.页面状态如何切换
React Portals
1.写弹窗使用的api
2.可以将虚拟 DOM 映射到任何真实 DOM 节点
3.解决了漂浮层的问题,比如Dialog,Tooltip 等
renderDialog() {
return (
<div className="portal-sample">
<div>这是一个对话框!</div>
<br />
<Button
type="primary"
onClick={() => this.setState({ visible: false })}
>
关闭对话框
</Button>
</div>
);
}
render() {
if (!this.state.visible) return this.renderButton();
return ReactDOM.createPortal(
this.renderDialog(),
document.getElementById("dialog-container"),
);
}
}
一个拖放组件
import React, { Component } from "react";
require("./DndSample.css");
const list = [];
for (let i = 0; i < 10; i++) {
list.push(`Item ${i + 1}`);
}
const move = (arr, startIndex, toIndex) => {
arr = arr.slice();
arr.splice(toIndex, 0, arr.splice(startIndex, 1)[0]);
return arr;
};
const lineHeight = 42;
class DndSample extends Component {
constructor(props) {
super(props);
this.state.list = list;
}
state = {
dragging: false,
draggingIndex: -1,
startPageY: 0,
offsetPageY: 0,
};
handleMounseDown = (evt, index) => {
this.setState({
dragging: true,
startPageY: evt.pageY,
currentPageY: evt.pageY,
draggingIndex: index,
});
};
handleMouseUp = () => {
this.setState({ dragging: false, startPageY: 0, draggingIndex: -1 });
};
handleMouseMove = evt => {
let offset = evt.pageY - this.state.startPageY;
const draggingIndex = this.state.draggingIndex;
if (offset > lineHeight && draggingIndex < this.state.list.length - 1) {
// move down
offset -= lineHeight;
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex + 1),
draggingIndex: draggingIndex + 1,
startPageY: this.state.startPageY + lineHeight,
});
} else if (offset < -lineHeight && draggingIndex > 0) {
// move up
offset += lineHeight;
this.setState({
list: move(this.state.list, draggingIndex, draggingIndex - 1),
draggingIndex: draggingIndex - 1,
startPageY: this.state.startPageY - lineHeight,
});
}
this.setState({ offsetPageY: offset });
};
getDraggingStyle(index) {
if (index !== this.state.draggingIndex) return {};
return {
backgroundColor: "#eee",
transform: `translate(10px, ${this.state.offsetPageY}px)`,
opacity: 0.5,
};
}
render() {
return (
<div className="dnd-sample">
<ul>
{this.state.list.map((text, i) => (
<li
key={text}
onMouseDown={evt => this.handleMounseDown(evt, i)}
style={this.getDraggingStyle(i)}
>
{text}
</li>
))}
</ul>
{this.state.dragging && (
<div
className="dnd-sample-mask"
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
/>
)}
</div>
);
}
}
export default DndSample;
//css
.dnd-sample ul {
display: inline-block;
margin: 0;
padding: 0;
background-color: #eee;
}
.dnd-sample li {
cursor: default;
list-style: none;
border-bottom: 1px solid #ddd;
padding: 10px;
margin: 0;
width: 300px;
background-color: #fff;
}
.dnd-sample-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
}
性能场景
- react-loadable 实现React 异步加载,代码的分包。
- reselect 实现数据缓存 如:搜索框
异步渲染
时间分片
- 虚拟DOM的diff操作 可以分片进行
- React新API:unstable_deferredUpdates
- Chrome 新API:requestIdleCallback
渲染挂起
- 使用的是 React中的 Timeout 组件;
- React新API:unstable_deferUpdate