xxx脚手架 创建一个基于xxx库的模板项目
react提供了一个用于创建react项目的脚手架库: create-react-app
项目的整体技术架构为: react + webpack + es6 + eslint.....
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
react脚手架项目结构
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)
文档对象模型 (DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。
虚拟DOM自然就是跟DOM有很大关系的了。我们在使用原生JS开发或者使用Jquery开发,经常就会操作DOM,但是我们使用的时候发现,每次我们改变DOM的时候,页面再次渲染,会花费不短的一段时间,这样用户体验就不太好了。如果我们每次操作的不是DOM或者每次只操作更少的DOM呢,是不是会花费的时间更短呢,基于这个想法,就有了虚拟DOM。
在React中,会把DOM转换成JavaScript对象,然后再把JavaScript对象转化成DOM,这样我们对于DOM的操作,实际上是在操作这个JavaScript对象。
一种JavaScript语法扩展
本质上,JSX为我们提供了创建React元素方法(React.createElement(component, props, ...children))的语法糖(syntactic sugar)。
代码实质上等价于:
var element = React.createElement(
"h1",
null,
"Hello, world!"
);
在if语句中使用JSX,并将JSX作为函数返回值。实际上,这些JSX经过编译后都会变成JavaScript对象。
function getGreeting(user) {
if (user) {
return Hello, {formatName(user)}!
;
}
return Hello, Stranger.
;
}
经过babel会变成下面的js代码:
function test(user) {
if (user) {
return React.createElement(
"h1",
null,
"Hello, ",
formatStr(user),
"!"
);
}
return React.createElement(
"h1",
null,
"Hello, Stranger."
);
}
在JSX中使用JavaScript表达式
JSX属性值 JSX的Children 传值
JSX可自动防范注入攻击 避免xss攻击
JSX中的props (JavaScript表达式 字符串 拓展运算符)
注意事项
使用JSX时要引入React库
注意引入JSX中用到的自定义组件
自定义组件首字母一定要大写
元素标签名不能使用表达式
事件名都是用小驼峰格式进行书写,例如onclick要改写成onClick
class ShowAlert extends React.Component {
showAlert() {
console.log("Hi");
}
render() {
return ;
}
}
this指向问题(解决)
render方法中使用bind
如果使用一个类组件,在其中给某个组件/元素一个onClick属性,它现在并会自定绑定其this到当前组件,解决这个问题的方法是在事件函数后使用.bind(this)将this绑定到当前组件中
这种方式在组件每次render渲染的时候,都会重新进行bind的操作,影响性能
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
test
)
}
}
render方法中使用箭头函数
通过ES6的上下文来将this的指向绑定给当前组件,同样再每一次render的时候都会生成新的方法,影响性能
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
this.handleClick(e)}>test
)
}
}
constructor中bind
在constructor中预先bind当前组件,可以避免在render操作中重复绑定
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this > ', this);
}
render() {
return (
test
)
}
}
定义阶段使用箭头函数绑定
能够避免在render操作中重复绑定,如下:
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log('this > ', this);
}
render() {
return (
test
)
}
}
上述四种方法的方式,区别主要如下:
编写方面:方式一、方式二写法简单,方式三的编写过于冗杂
性能方面:方式一和方式二在每次组件render的时候都会生成新的方法实例,性能问题欠缺。若该函数作为属性值传给子组件的时候,都会导致额外的渲染。而方式三、方式四只会生成一个方法实例
综合上述,方式四是最优的事件绑定方式
React生命周期指的是组件从创建到卸载的整个过程每个过程都有对应的钩子函数它主要有以下几个阶段挂载阶段组件实例被创建和插入Dom树的过程,更新阶段组件被重新渲染的过程,卸载阶段组件从Dom树中被删除的过程。
观察生命周期函数的执行流程组件初始化,父节点修改子节点的props子节点修改内部状态state组件强制刷新组件卸载React可能会在porps传入时即使没有发生改变的时候也发生重新渲染。
生命周期和setState是react的基础组成部分,在我们日常开发中经常被使用是上面初始化的分支很明显加了一些的状态说明综合生命周期状态生命状态它是生命周期中关键的状态setState是否出发更新就是依据它的状态。
React16+
始化阶段(Initialization)
组件的构造函数(constuctor)部分,继承React Component,在constructor中通过super(props)调用父类React Component的构造函数,才拥有了之后的生命周期。
constructor
在这个阶段,会获取到外层传递的props数据,这里进行组件初始化的工作,内部state的定义,和组件本身逻辑的初始化。
挂载阶段(Mounting)
componentWillMount
在组件被挂载到DOM上之前调用,原本在这里也是做一些初始化工作,和调用接口获取初始数据,但是由于和constructor的工作重复,且若在服务端渲染,componentWillMount会在服务端和客户端各自执行一次,这会导致请求两次,而接下来的componentDidMount这会在客户端进行,而且之后有了Fiber之后,由于任务可以中断,componentWillMount可能会被执行多次。
所以再React16之后,这个生命周期被废弃了,初始化的工作,在constructor中处理,获取初始数据的工作在componentDidMount里处理。
Render
组件的渲染阶段,props或state有更新的时候,如果没有在shouldComponentUpdate中禁止的话,会触发重新渲染,而DOM层的实际重绘过程是一个复杂的过程,这个过程React会通过虚拟DOM的方式和复杂算法进行处理,这里不做赘述,后续文章会有介绍。render函数是一个纯函数,它的返回只依赖传递的参数。这里不能进行state的更新处理,可能会导致无限循环。
componentDidMount
在组件被挂载到DOM上之后调用,只会调用一次,异步数据的获取工作在这里处理。
更新阶段(UpdatIon)
在父组件重新render或者传入的props或内部的state有更新时,都会进入更新阶段。
componentWillReceiveProps
在React16之前,这个生命周期可是非常常用,对于外部传递的props的响应工作在这里处理,比如我的props里传了一个数值变量num,本来num = 1,外部有更新将num变为2,会触发当前组件内也进行更新,在componentWillReceiveProps阶段,我可以获取到prevProps和nextProps,这样我就可以比对,是哪个值变化导致的更新,就可以针对这个变化运行我内部的处理逻辑,可以将外部传的props更新成内部使用的state。
但通过这个方式来处理props和state的关系十分不优雅,这样不但会破坏state的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。所以这个生命周期在React16之后也废弃了,这里的工作交由新的声明周期getDerivedStateFromProps和componentDidUpdate来处理。
shouldComponentUpdate
大量的数据更新会触发组件一遍又一遍的更新,会造成不小的性能损耗,这里就需要shouldComponentUpdate来进行优化工作,它有两个参数,nextProps和nextState,在这里通过传的更新后数据和当前数据this.props、this.state里的数据进行比对,可以筛选,哪些变化可以触发重绘,哪些就行拦截,如果拦截,return false。如果返回true,则先进行React elements比对,如果相同,则不会触发重绘,如果不同再进行绘制。
componentWillUpdate
这个方法会在render之前调用,可以处理一些更新前需要处理的工作。在React16之后会结合新生命周期getDerivedStateFromProps一起来处理props和state的同步问题。
render
重新渲染
componentDidUpdate
这个方法会在render之后调用,在这里可以操作更新后的组件DOM。
卸载阶段(Unmounting)
componentWIllUnmount
在组件被卸载前调用,这里会处理一些数据清理、定时器清理等工作来避免内存泄露。
react16+
React为了优化因为复杂层次深的组件树的更新导致的跳帧问题,进行了优化,推出了React Fiber,它通过调用requestIdleCallback方法,可以中断主线程中正在执行的任务,将使用权交由渲染层使用。因为任务的可中断,之前的生命周期会受到影响,可能会导致周期函数多次调用,所以再React16后对生命周期也做了改变。
之前在render之前会调用的方法有componentWillMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate,除了shouldComponentUpdate之外的三个方法都废弃,用getDerivedStateFromProps来替代。
新增两个生命周期
static getDerivedStateFromProps(getDSFP)
在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
首先这个新的方法独特的地方是static,它是一个静态方法,在这里不能调用this,也就是一个纯函数,开发者使用时不能写出副作用的代码,这是React通过Api来约束开发者写出更好的代码。它传了两个参数,一个是新的 nextProps ,一个是之前的 prevState ,所以开发者只能通过prevState而不是 prevProps 来做对比,保证了 state 和 props 之间的简单关系以及不需要处理第一次渲染时 prevProps 为空的情况。也基于以上两点,将原本 componentWillReceiveProps 里进行的更新工作分成两步来处理,一步是 setState 状态变化,更新 state 在 getDerivedStateFromProps 里直接处理,另一步是昂贵操作,即我们自己的运行逻辑,在 componentDidUpdate 里来处理。
与 componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据 props 的变化去触发一些回调。但不论是 componentWillReceiveProps 还是 componentWillUpdate,都有可能在一次更新中被调用多次,也就是说写在这里的回调函数也有可能会被调用多次,这显然是不可取的。与 componentDidMount 类似,componentDidUpdate 也不存在这样的问题,一次更新中 componentDidUpdate 只会被调用一次,所以将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate 就可以解决这个问题。本段引用自React v16.3 版本新生命周期函数浅析及升级方案。
getSnapshotBeforeUpdate
在render之后,可以读取但无法调用DOM的时候调用。它返回的值作为 componentDidUpdate 的第三个参数。
如果我们需要获取DOM元素状态,但是由于在fiber中,render可打断,可能在 componentWillMount 中获取到的元素状态很可能与实际需要的不同,这里就需要 getSnapshotBeforeUpdate 这个新增的生命周期函数来解决。
与 componentWillMount 不同的是,getSnapshotBeforeUpdate 会在最终确定的render执行之前执行,也就是能保证其获取到的元素状态与componentDidUpdate中获取到的元素状态相同。这里引入官方提供的一段参考代码:(类似:componentWillReceiveProps)
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {favoritesite: "runoob"};
}
componentDidMount() {
setTimeout(() => {
this.setState({favoritesite: "google"})
}, 1000)
}
getSnapshotBeforeUpdate(prevProps, prevState) {
document.getElementById("div1").innerHTML =
"在更新前喜欢的网站是:" + prevState.favoritesite;
}
componentDidUpdate() {
document.getElementById("div2").innerHTML =
"更新后喜欢的网站是:" + this.state.favoritesite;
}
render() {
return (
我喜欢的网站是 {this.state.favoritesite}
);
}
}
ReactDOM.render( , document.getElementById('root'));
为什么要用hooks? 组件性能函数组件要高于类组件,或者说是高阶组件性能高,但是高阶组件HOC有两个问题:
所以出现了hooks(16.8+)
react hooks的缺点
useState
使函数式组件也能保存状态的一个hook,这个hook的入参是状态的初始值,返回值是一个数组,数组里第一个参数为状态的值,第二个参数为修改状态的方法。
useEffect
可以模拟组件挂载完成、更新完成、即将卸载三个阶段,即componentDidMount、componentDidUpdate、componentWillUnmount。
useEffect的一个参数为函数,表示组件挂载、更新时执行的内容,在函数里再返回一个函数,表示组件即将卸载时调用的函数。第二个参数为可选项,可传入数组,数组里可以为空,表示不依赖任何状态的变化,即只在组件即将挂载时执行,后续任何状态发生了变化,都不调用此hook。数组里也可以定义一或多个状态,表示每次该状态变化时,都会执行此hook。
useEffect(()=>{
return ()=>{
// 这样模拟的是 componentWillUnmount
}
}, [])
useContext
在没有hook之前,我们通常都会通过 xxxContext.Provider 和 xxxContext.Consumer 的方式来传递和获取context的值,使用hook之后,传递context的方式不变,但子元素获取context的方式变得更加的简洁。
--创建myContext对象,myContext对象返回一个Provider React组件,接收value属性传递给子组件
import { createContext, useState } from 'react';
export const myContext = createContext(null);//初始值为null
const Parent = () => {
const [count,setCount] = useState(0);
return (
)
}
export default Parent;
import { useContext } from 'react';
import { myContext } from './Parent';
const Child = () => {
let count = useContext(myContext);
return (
count
)
}
export default Child;
useRef
用于dom元素或者组件
// 聚焦输入框
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
})
return (
)
}
useReducer
useReducer相当于是useState的升级版,作用与useState类似,都是用来保存状态,但它的不同点在于可以定义一个reducer的纯函数,来处理复杂数据。
import React, { useReducer } from 'react';
function Reducers () {
const [count,dispatch] = useReducer((state,avtion) => {
switch(avtion.type) {
case 'add':
return state+1;
case 'minus':
return state-1
default:
return state
}
},0)
return (
{count}
)
}
export default Reducers
useCallback
函数式组件中,每一次更新状态,自定义的函数都要进行重新的声明和定义,如果函数作为props传递给子组件,会造成子组件不必要的重新渲染,有时候子组件并没有使用到父组件发生变化的状态,此时可以使用useCallback来进行性能优化,它会为函数返回一个记忆的值,如果依赖的状态没有发生变化,那么则不会重新创建该函数,也就不会造成子组件不必要的重新渲染。
useCallback应该和React.memo配套使用,缺了一个都可能导致性能不升反而下降。
useMemo
useMemo也是返回一个记忆的值,如果依赖的内容没有发生改变的话,这个值也不会发生变化,useMemo与useCallback的不同点在于useMemo需要在传入的函数里需要return 一个值,这个值可以是对象、函数。
useImperativeHandle
这个是与forwardRef配合来使用的,当我们对函数式组件使用forwardRef将ref指定了dom元素之后,那就父组件就可以任意的操作指定的dom元素,使用useImperativeHandle就是为了控制这样的一种行为,指定父元素可操作的子元素的方法。
useLayoutEffect
这个方法与useEffect类似,只是执行的顺序稍有不同,useEffect是在组件渲染绘制到屏幕上之后,useLayoutEffect是render和绘制到屏幕之间。