传统框架存在问题
1.传统 UI操作关注太多细节
DOM API 繁多,需要手动调用,且要经过多步骤进行更新
react 解决办法:始终整体‘刷新’页面 ,无需关心细节,(不管谁是哪部分新增或更改的,根据现在状态整体刷新页面)
2.应用程序状态分散在各处f难以追踪和维护
传统数据模型存在问题:传统 MVC结构复杂,难以扩展和维护
react 解决办法:Flux 单向数据流
创建组件
1.创建静态 UI
2.考虑组件的状态组成 :是由state内部维护还是用外部传进来的props维护
3.考虑组件的交互方式:内部进行了操作,如何告知/把状态暴露到父组件
受控组件VS非受控组件
受控组件: 表单元素状态由使用者维护
组件发生变化,利用onchange将状态给到到父组件(使用者),父组件更新状态,再通过props传递状态给组件,组件重新渲染
非受控组件:表单元素状态 DOM自身维护
创建原则:单一职责原则
1.每个组件只做一件事
2.如果组件变得复杂,那么应该拆分成小组件
拆分成小组件好处:
a.降低复杂度;
b.提高性能,如果一个组件足够大之后,任何一种状态变化,都会引起整个组件刷新,
拆分成小组件后,未涉及到的小组件,没有状态变化,就不会刷新
数据状态管理:DRY(Don’t Repeat Yourself)原则
1.能计算得到的状态就不要单独存储 :
2.组件尽量无状态,所需数据通过 props 获取
JSX
本质:不是模板语言,而是一种语法糖(相当于使用React.createElement创建组件)
优点:
1.声明式创建界面的直观(可以使用自定义和原生标签直接进行创建,方便构建UI)
2.代码动态创建界面的灵活 (节点的创建,属性更改,消失完全可以向写JS一样进行操作)
3.无需学习新的模板语言(JS+JSX特性)
约定:自定义组件以大写字母开头
1.react 认为小写的 tag是原生 dom 节点,如 div
2.大写字母开头为自定义组件
3.JSX 标记可以直接使用属性语法,例如<meau.Item />,使用属性语法,可以不以大写字母开头
JSX防注入攻击
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。
所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击
生命周期方法
constructor
js标准类的构造函数
1.用于初始化内部状态,很少使用(初始化工作一般会在其他声明周期内使用)
2.唯一可以直接修改state的地方(可以这么写this.state.xxx = ‘abc’)
getDerivedStateFromProps
react 16.3新引入的API
从属性props初始化内部状态state
1.当 state 需要从 props 初始化时使用
2.尽量不要使用:维护两者状态一致性会增加复杂度
如果state需要从props获得,一般使用props可以通过动态计算得到,如果用state进行单独存储,就有必要时刻维护二者的一致性
维护一致性则增加了复杂度,容易出BUG
3.每次 render都会调用
该方代替原来的componentwillreceiveProps方法
4.典型场景:表单控件获取默认值
初始值来自props,用户输入后为用户输入的值
componentDidMount
1.UI渲染完成后调用
2.只执行一次
3.典型场景:获取外部资源(只需要获取一次资源)
componentWillUnmount
1.组件移除时被调用
2.典型场景:资源释放
getSnapshotBeforeUpdate
1.在页面 render之前调用,state 已更新
2.典型场景:获取 render之前的 DOM 状态
componentDidUpdate
捕获每一次更新
1.每次 UI 更新时被调用
2.典型场景:页面需要根据 props 变化重新获取数据
shouldComponentUpdate
1.组件决定 Virtual DOM是否要重绘
2.一般可以由 PureComponent 自动实现
3.典型场景:细节性能优化
在react 16.3中去掉的方法有
componentWillMount
componentWillReceive
componentWillUpdate
状态更新可能是异步的1
2
3
4
5
6
7
8//此代码可能无法更新计数器
this.setState({
counter: this.state.counter + this.props.increment,
});
//更改为以下写法即可
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
1.算法复杂度O(n):通过广度优先分层比较/遍历实现线性算法
2.虚拟 DOM如何计算 diff
虚拟 DOM的两个假设
a.组件的 DOM结构是相对稳定的
b.类型相同的兄弟节点可以被唯一标识
根结点比较
属性变化及顺序:利用标识调整位置
节点类型发生变化:删除原有结点,构建新类型结点
节点跨层移动:删除原有结点,在应有(新)位置构建相同类型新结点
3.key属性的作用:提高性能
组件复用设计模式
1.高阶组件
传入原始组件,结合生命周期创建一些额外的功能,然后附加到原始组件上,返回增加功能后的原始组件
额外的功能一般都是一些通用的功能,避免在原始组建上增加额外功能1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import React from "react";
export default function withTimer(WrappedComponent) {
return class extends React.Component {
state = { time: new Date() };
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
time: new Date()
});
}
render() {
return <WrappedComponent time={this.state.time} {...this.props} />;
}
};
}
2.函数子组件
将子组件以函数返回值形式作为children属性传入原始组件,在原始组件中,在需要显示原始组件的地方执行函数,
或者在合适的生命周期中执行函数拿到多个子组件,再决定在什么位置显示不同的子组件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//写法一
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
//写法二
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
//在原始组件应用子组件
class Mouse extends React.Component{
render(){
<div>
{this.props.children({x:50,y:100})}
</div>
}
}
context API
应用场景:全局性的一些字段,如theme的切换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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63import React from "react";
const enStrings = {
submit: "Submit",
cancel: "Cancel"
};
const cnStrings = {
submit: "提交",
cancel: "取消"
};
//传入原始数据/默认值,创建context
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 (
// context的 Provider属性是一个组件,包裹需要用到context数据子组件的组件,其value值为相应在子组件中需要用到的数据
<LocaleContext.Provider value={this.state.locale}>
<button onClick={this.toggleLocale}>
切换语言
</button>
{this.props.children}
</LocaleContext.Provider>
);
}
}
class LocaledButtons extends React.Component {
render() {
return (
//context的 Consumer属性也是一个组件,包裹需要使用context数据的子组件,
<LocaleContext.Consumer>
{locale => (//local 即Context.Provider 的value值
<div>
<button>{locale.cancel}</button>
<button>{locale.submit}</button>
</div>
)}
</LocaleContext.Consumer>
);
}
}
export default () => (
<div>
<LocaleProvider>
<div>
<br />
<LocaledButtons />//使用context数据的组件必须为Provider组件的子组件,
</div>
</LocaleProvider>
<LocaledButtons />//如果子组件在Context.Provider形成组件的外面使用,则只会应用默认数据,不会随Value属性发生变化
</div>
);
为什么需要脚手架
react:进行UI开发
redux:进行状态的管理
react/router:进行路由的管理
babel:把最新的js特性翻译成兼容浏览器的旧的JS语法
webpack:进行打包
eslint:语法检查
因而需要引入以上几个技术栈的几十个个package,不但要了解,还要知道如何让进行相应的配置
在开启一个项目时,配置过程做完一次基本/只要实现一次,就基本可以复用,用来再开启新项目
所以为方便开发,用脚手架实现配置过程
脚手架有:
Create React App 基础(webpack,)
Rekit 开源项目
Codesandbox 在线开发工具
dva 蚂蚁金服的一套脚手架工具
为什么需要打包
1.编译ES6语法特性,编译JSX(保证浏览器可以兼容执行)
2.整合资源,例如图片,Less/Sass,svg
3.优化代码体积(对变量名进行缩短,去除注释,去除空格,去除不必要的变量等等)
webpack
将项目中所有资源进行整合,将每种资源(js,png,sass…)使用插件的方式载入对应的Loader,
loader能够决定自己支持什么样的语法,loader的输出是可以执行的js语言,可以兼容多平台多版本浏览器
可以部署的语言
打包注意事项
1.设置nodejs环境为production
开发环境可能需要检查输入的参数的类型,语法,拼写等问题,但生产环境不需要
2.禁用开发时专用代码,比如logger
打包时可以根据环境判断决定是否执行一定的代码,所以开发用到的代码在打包时不应被打到生产环境,要做区分
3.设置应用根路径
告诉应用到哪里获取地址