目录
1 简介
1.1 React是什么?
JavaScript库,可用于创建界面;基于Component(组件),所有页面元素都被抽象成组件,大部分操作都是针对组件的。
--》基于组件和虚拟dom原理的js库。
1.2 优势
- 组件可复用性和扩展性好,开发效率高;
- 不直接操作DOM;
- 在虚拟dom中进行计算变化
- 将diff反应到真实DOM中,尽可能减少绘制,提高性能
- 基于状态实现对DOM控制和渲染。
- 是一大创新;
- 将组件看成一个状态机;
- 一开始有一个初始状态;
- 用户互动,导致状态变化;
- 触发重新渲染UI。
1.3 虚拟DOM原理
实现过程
- React将新的虚拟DOM(由组件的render返回,也就是真实DOM未来要显示的模样)与旧的虚拟DOM(结构、数据与当前真实的DOM一致)做比较,生成差异diff
- 将差异diff应用到当前的真实DOM中
- 当前真实的DOM更新为新的真实DOM(结构、数据与新的虚拟DOM一致)
React完成全过程,开发者只需要在组件render中返回新的虚拟DOM即可。
注意点
- 组件是虚拟DOM,不是真实的DOM节点,是存在内存中的一种数据结构。只有它插入文档以后,才会变成真实的DOM节点。
- 为了与浏览器交互,我们有时候需要用到真实DOM节点,可以通过React.findDOMNode(components)获取组件中的真实DOM
- React.findDOMNode()只在mounted组件中调用,mounted组件就是已经渲染在浏览器DOM结构中的组件,在组件render()中调用会发生异常,因为此刻是虚拟DOM
2 JSX
是js的语法扩展。实例:
const element = <h1>Hello world !</h1>
jsx语法
特点:
- 在JS中直接使用html标签;
- 可用可不用。
属性值:
- 值为字符串;
- 若在标签内,加“”
- 若在标签对内,不加“”
- 值为js表达式
- 表达式用{}包起来,不加“”
- style属性是第一是变量,第二是对象使用,需要两个{{}}
- 值为布尔值
- 作为表达式处理,需要加{}
注意点:
- 使用大小写的约定区分本地组件的类和HTML标签
- 某些html为js的保留字,需重新命名
- class——className
- for——htmlFor
- 事件名为驼峰写法
- false true null undefined不会被渲染,如果需要使用的化,需转为字符串
<div>My js variable is {String(myVariable)}</div>
3 元素
- 元素是不可变对象,一旦被创建,就无法更改它的子元素或者属性。
- 更新UI的方式,就是创建全新的元素,并传入ReactDom.render()中。
- 但是,React只会更新实际改变的内容
4 组件
4.1 分类
函数组件:编写js函数
function Welcome(props){
return <h1>Hello World!</h1>;
}
class组件:继承ES6 React.Component的class
class Welcome extends React.Component{
render(){
return <h1>Hello World!</h1>;
}
}
两者等效。
4.2 组件类
4.2.1 简介
1.变量声明、引入的外部依赖import;
2.定义组件类class。
生命周期函数
- constructor //创建期
- componentWillMount //创建期
- componentDidMount //创建期
- componentWillReceiveProps //特殊状态处理函数,更新期
- shouldComponentUpdate //更新期
- componentWillUpdate //更新期
- componentDidUpdate //更新期
- componentWillUnMount //销毁期
状态机,随着props和state的改变,DOM表现形式发生变化
- 创建期——Mount
- constructor:构造函数,可访问this.props和设置初始化state
- componentWillMount:组件首次被渲染前调用,可在组件被首次render前最后一次修改state
- render:返回虚拟DOM
- componentDidMount:组件已在真实DOM中首次渲染后调用,可使用ReactDOM.findDOMNode()获得真实DOM并操作,如为事件绑定监听函数,设置定时器,发送JAX请求
- 更新期——Update
- componentWillReceiveProps:组件收到新的props调用时,可修改state及决定是否更新props
- shouldComponentUpdate:接收到新的props或者state,将要重新渲染前调用,可用于优化react渲染速度,返回true,继续向下执行,返回false停止渲染
- componentWillUpdate:shouldComponentUpdate返回true后调用,不能在此处调用setState
- render
- componentDidUpdate:已在真实DOM中渲染后调用,可操作真实DOM
- 销毁期——Unmount
- componentWillUnmount:组件在DOM中移除后立刻被调用,此处进行必要的清理
render方法
- 必须存在的;
- 返回一个虚拟DOM节点,是一个js对象,返回值可以是null、false或者React组件;
- 只负责返回虚拟DOM高速React新的DOM的样子;
- 组件创建期(仅一次)、更新期(可重复)均会被调用,且在更新期,属性(this.props)或者状态(this.state)的变化都有可能触发render被调用;
- 使用方法的注意点:
- 不修改组件属性和状态值
- 只能返回一个顶级组件,不能返回一组元素
- 不能调用React.findDOMNode()
3.组件输出 export
组件实例化,需要用到ReactDom.render()方法;一般页面中最顶层组件才会使用,将所有组件一起实例化。
4.2.2 组件的props(属性)和state(状态)
- 组件间的状态传递——props :从父组件到子组件的数据传递
- 数据流单向——从父到子
- 当父组件传递的props改变时,React会向遍历整个组件树,并重新渲染使用这个props的组件
- setProps()只能在组件外调用
- 组件内部的this.props是只读的
- 组件内部的状态——state :组件私有,只能在组件内部修改
- 只用于存储简单的视图状态
- 可读可改,通过this.state访问和初始化,只能通过setState()修改,且只能在组件内使用
- 相同点
- 都是纯JS对象
- 都会触发render更新
- 都具有确定性
4.2.3 组件间的通信
- 无父子关系的组件间的通信:
- 订阅(subscribe)/监听(listen)一个事件通知,并发送(send)/触发(trigger)/发布(publish)/发送(dispatch)事件通知想要的组件
- 父组件->子组件:设置props的方式
- 子组件->父组件
- 回调函数作为子组件的属性
- 父组件中定义回调函数,并在父组件render方法中将回调函数作为子组件的属性传递给子组件
- 子组件通过该属性向父组件回传数据
注意点:
- 自定义组件,首字母必须大写,React将小写字母开头的组件视为原生DOM标签
- 组件刷新的顺序
- 当组件传给ReactDom.render()时,会调用组件的构造函数,构造函数中有this.state的初始值
- React会调用render()方法,确定在页面上展示什么,然后React会更新DOM来匹配组件的渲染输出
- 当组件被插入到DOM中后,会调用ComponentDidMount()生命周期方法,调用相关的方法
- 通过setState方法计划对UI的更新,React得知state发生变化,会重新调用render()方法进行渲染,react会相应地更新DOM
- 状态提升
- 多个组件需要反应相同的变化数据,将共同状态提升到最近的共同父组件中去
- 按照从上到下的数据流,找到最近的共同父组件
- 如果某些数据可以由props或state推导出来,那就不应该存在于state中
- 可使用React开发者工具来检查问题组件的props,定位导负责更新state的组件,来定位问题
- 类的组合和继承
- 组合:React不支持slot概念,可以用props进行传递
<div className="left">
{props.left}
</div>
<组件 left={<组件 />}/>
2.继承——尽量不使用
- Props和组合提供清晰而安全地定制组件外观和行为的灵活方式
- 组件可以接收任意props,包括基本数据类型、React元素、函数
- 在组件间复用非UI的功能,建议将其提取为一个单独的JS模块,如函数、对象或类,组件可以直接引入(import)而无需继承
5 React哲学
5.1 开发步骤
- 已知设计稿和API数据格式
- 将设计好的UI划分为组件层级
- 根据单一功能原则来判断组件的范围
- 一个组件原则上只负责一个功能
- 多个功能,考虑将组件拆分为更小的组件
- 父子组件角色确认
- 用React创建一个静态版本
- 不包含交互功能的UI实现
- 通过props传入的数据,不应该使用state构建静态版本
- 自上而下或自下而上的构建应用
- 当应用比较简单的时候,自上而下更方便
- 大型项目,自下而上,并同时为低层组件编写测试
- 组件仅只需提供render()方法用于渲染
- 确定UI state的最小且完整表示(交互)
- 只保留应用所需的可变state的最小集合,其他数据由计算产生
- 三个问题判断是否state
- 该数据是否由父组件通过props传递而来的?
- props
- 该数据是否随着时间的推移而保持不变
- 否
- 能否根据其他state或props计算出该数据
- 否
- 该数据是否由父组件通过props传递而来的?
- 确定state放置的位置
- 哪些组件拥有这些state
- 如何确定?
- 找到根据这个state进行渲染的所有组件
- 找到他们的共同所有者组件
- 该共同所有者或则比它层级更高的组件应该拥有该组件
- 如果找不到合适的位置来存放该state,可以创建一个新的组件来存放该state,并将该组件置于高于共同所有者组件层级的位置
- 添加反向数据流
- 处于较低层级的组件更新较高层级的组件中的state
5.2 代码分隔
5.2.1 打包
将一个文件引入并合并到一个单独文件的过程,最终形成一个bundle,接着在页面上引入此bundle,整个应该即可一次性加载
打包前:
// app.js
import { add } from './math.js';
console.log(add(16, 26)); // 42
//math.js
export function add(a,b){
return a+b;
}
打包后:
function add(a,b){
return a+b;
}
console.log(add(16,26));
5.2.2 代码分隔
为了解决当代码包体积过大,导致加载时间过长的问题;代码分隔能帮助“懒加载”当前用户所需要的内容,能显著提高性能。
方法: import()
分隔前:
import {add} from './math';
console.log(add(16,26));
引入后:
import('./math').then(math=>{
console.log(math.add(16,26));
})
5.2.3 懒加载——React.lazy()
使用前:
import OtherComponent from './OtherComponent';
使用后:
const OtherComponent = React.lazy(()=> import ('./OtherComponent'));
具体实现过程:
- 在组件首次渲染时,自动导入包含otherComponent组件的包
- 接收一个函数,这个函数需动态调用import(),返回值为一个Promise,该Promise需要resolve一个default export的React组件
- 应在Suspense组件中渲染lazy组件,如此可使用在等待加载lazy组件时做一些loading指示等
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
fallback属性接收任何在组件加载中想展示的React元素。
可使用ErrorBoundaries技术处理加载异常问题
不支持服务端渲染
5.2.4 基于路由的代码分隔
决定在哪里引入代码分隔。从路由开始(React Rounter路由)
const App = () =>{
<Router>
<Suspense></Suspense>
</Router>
}
6 Context
提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法
- 典型的React应用,数据是通过props属性自上而下进行传递的
- context提供了组件中共享某些属性的方式
- 全局属性
避免中间层层进行props数据传递。
const ThemeContext = React.createConext('light');//默认值为light
class App extends React.Component {
render(){
return (
<ThemeContext.Provider value='dark'>
<Toolbar /> //组件树,使用Provider来将value的值传递给Toolbar这个组件,它的自组件都能拿到这个value的取值
</ThemeContext.Provider>
);
}
}
function Toolbar(props){//中间组件不需要明确传递theme的取值
return (
<div><ThemeButton /></div>
);
}
class ThemeButton extends React.Component{
static contextType = ThemeContext;//读取当前的theme context,React会向上查找到最近Provider,然后使用它的值,即‘dark’
render(){
return <Button theme={this.context}></Button>
}
}
7 错误处理
7.1 错误边界 Error Boundaries
7.1.1 简介
- 是一种React组件。
- 可以捕获并打印发生在其子组件树上发生的js错误,并会渲染出备用UI
- 在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误
- 工作方式类似js中的catch{},但是只针对class组件
7.1.2 不适用于以下错误场景
- 事件处理
- 异步代码,setTimeout或requestAnimationFrame回调函数
- 服务端渲染
- 它自身抛出来的错误
7.1.3 如何使用
class组件中定义了static getDrivedStateFromError() 或componentDidCatch()这两个生命周期方法中的任何一个,它就变成了一个错误边界
- 使用static getDrivedStateFromError()用了渲染备用UI
- componentDidCatch打印错误信息
当作常规组件去使用即可。
7.2 try/catch
仅用于命令式代码,处理事件处理的错误。
try{
showButton();
} catch(error){}
8 性能优化
- 使用生产版本:非生产版本默认包含了很多警告信息,使得变大变慢
- 避免重复渲染
- 重写shouldComponentUpdate来进行优化,默认返回true,让React执行更新。当明确知道何时不需要更新,可以在shouldComponentUpdate返回false来跳过渲染,包括render的调用及之后的操作
- 继承React.PureComponent,实现浅比较,当props和state的所有字段发生变化时,进行渲染
- 当props或者state某种程度是可变的话,浅比较会有遗漏,不适合用此方式
- 浅比较,当对比的类型为object,且key长度相等时,浅比较仅仅是用Object.is()对它的value做了基本数据类型的比较,所有key里面是对象的话,会出现比较结果不正确的情况
- 避免更正用于props或state的值
- Profiler:分析渲染的代价(工具)
9 严格模式
是用来突显应用程序中潜在问题的工具。
不会渲染任何可见UI,为其后代元素触发额外的检查和警告。
使用方式:
<div>
<Header>
<React.StrictMode>
<div><ComponentOne /><ComponentTwo/></div>
</React.StrictMode>
</Header>
</div>
仅对ComponentOne和ComponentTwo及其后代元素进行检查
检查内容:
- 识别不安全的生命周期
- 使用过时的字符串ref API警告
- 使用废弃的findDomNode 方法的警告
- 检测意外的副作用
- 检测过时的context api
10 其他
10.1 使用PropTypes进行类型检查
import PropTypes from 'prop-types';
class Greeting extends React.Component{
render(){
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name : PropTypes.string //指定name为string
}
指定name为string
优化Flow或TypeScript=>静态类型检查器
默认值:
Greeting.defaultProps ={
name : 'stranger'
};
10.2 表单
10.2.1 受控组件
被React以state方式控制取值的表单输入元素叫做受控组件。
render(){
return ({
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange}/>
</label>
<input type="submit" value="提交"/>
</form>
});
}
10.2.2 非受控组件
表单数据将由Dom节点来处理,使用ref来从DOM节点中获取表单数据。
constructor(props){
super(props);
this.input = React.createRef();
}
render(){
return ({
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="提交"/>
</form>
});
}
优点:代码量少