受控组件
react中没有双向数据绑定,这种方式就类似于vue中的双向数据绑定。
//创建组件 class Login extends React.Component{ //初始化状态 state = { username:'', password:'' } saveUsername =(event) =>{ this.setState({username:event.target.value}) } savePassword =(event) =>{ this.setState({password:event.target.value}) } handleSubmit = (event) =>{ event.preventDefault() //阻止表单提交默认事件 const {username,password} = this.state alert(`你输入的用户名是${username},你输入的密码是${password}`) } render(){ return( <form onSubmit={this.hanleSubmit}> 用户名:<input type="text" name="username" onChange={this.saveUsername}/> 密码:<input type="password" name="password" onChange={this.savePassword}/> <button >登录</button> </form> ) } } //渲染组件 ReactDOM.render(<Login/>,document.getElementById('root'))
非受控组件(现用现取)
//创建组件 class Login extends React.Component{ handleSubmit = (event) =>{ event.preventDefault() //阻止表单提交默认事件 const {username,password} = this alert(`你输入的用户名是${username.value},你输入的密码是${password.value}`) } render(){ return( <form onSubmit={this.hanleSubmit}> 用户名:<input type="text" name="username" ref={c =>this.username = c}/> 密码:<input type="password" name="password" ref={c => this.password = c}/> <button >登录</button> </form> ) } } //渲染组件 ReactDOM.render(<Login/>,document.getElementById('root'))
高阶函数
如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数
-
若X函数,接收的参数是一个函数,那么X就可以称为高阶函数
-
若X函数,调用的返回值依然是一个函数,那么X就可以称为高阶函数
-
常见的高阶函数:Promise,setTimeout,arr.map()等等
//创建组件 class Login extends React.Component{ //初始化状态 state = { username:'', password:'' } saveFormData =(dataType) =>{ return (event) => { this.setState({[dataType]:event.target.value}) } } handleSubmit = (event) =>{ event.preventDefault() //阻止表单提交默认事件 const {username,password} = this.state alert(`你输入的用户名是${username},你输入的密码是${password}`) } render(){ return( <form onSubmit={this.hanleSubmit}> 用户名:<input type="text" name="username" onChange={this.saveFormData('username')}/> 密码:<input type="password" name="password" onChange={this.saveFormData('password')}/> <button >登录</button> </form> ) } } //渲染组件 ReactDOM.render(<Login/>,document.getElementById('root'))
函数柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
生命周期
生命周期钩子函数什么时候调用,与你书写的顺序无关。
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理(16.3之后),
你可以使用此
生命周期图谱
作为速查表
。
React经历版本迭代的过程中,有三种生命周期在演化,大致都可分为初始化,运行中和销毁阶段,第一版是15的时候,其中代表性的
getDefaultProps
、
getInitialState
早已废弃,我们不需要再做学习。
到16.4以前使用的部分生命周期函数在16.4及以后的版本中已经被逐步替代和废弃,在这里我们的学习方式会以16.4以后版本的生命周期作为主要学习的内容,其中也会简单学习一下16.4以前的部分生命周期以作扩展。
一、初始化阶段
在初始化阶段,组件实例被创建并插入 DOM 中,其生命周期调用顺序如下:
-
constructor
-
static getDerivedStateFromProps
-
UNSAFE_componentWillMount
-
render
-
componentDidMount
1.constructor
在类组件中,constructor本身是类的构造器,会在类实例化的第一步就执行,我们在这里可以做一些组件相关的事情,所以也属于生命周期的一个函数。
constructor的作用往往只有下面几种:
-
super(propps)
必须调用
,保证组件实例可以构建,并初始化props -
为this.state赋值, 其实也可以通过设置类的属性来做到这一点
-
为事件处理器绑定this,较为常用,毕竟class fields语法仅仅是实验性的
但是需要注意的是,constructor不是必须要写的,如果不进行上面的几种动作的话,就没必要给组件实现构造函数
在
constructor()
函数中
不要调用
setState()
方法
。如果你的组件需要使用内部 state,请直接在构造函数中为
this.state
赋值初始 state
:
constructor(props) { super(props); // 不要在这里调用 this.setState() this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); }
赋值只能在constructor中或者组件的属性上,其他地方只能利用setState去做数据的更改(不要在初次render前更改)。
有这样一种情况,我们想要根据传入的属性来更改组件自己的状态,这样的动作可以放置在constructor中吗?
constructor(props) { super(props); this.state = { count: this.props.num * 2 }; }
大家很容易就会看出问题,上面已经说了,constructor只在组件初始化的时候执行一次,后面都不会再执行了,这就可能会导致当传入的props改变后,对应的state并没有被更新,所以不要在这里做这样的事情,除非你只想让state的默认值为初次传入的props且后面不再依赖props的更新。
这样的事情是不合理的,React的理念中有一点是:
尽可能保证数据的唯一来源
,数据来源于props,那何苦在复制一份放在state里呢?
那么如果确实有这样的需求,那么可以从下面几个方面来解决:
-
强制重置 (不推荐, 性能消耗太大)
-
使用getDrivedStateFormProps
getDrivedStateFormProps在下面会讲到,接下来我们来感受一下所谓的:
强制重置
key可以为组件或元素做标识,当标识的key发生改变的时候,react会强制进行更新,也就是我们在外部给组件用key做标识后,更改key,组件强制更新的时候会再次执行constructor从而达到更新state的效果。
class Iner extends Component { constructor (props) { super(props); this.state = { ownCount: props.count } } render () { return ( <p>{ this.state.ownCount }!</p> ) } } class Outer extends Component { constructor (props) { super(props); this.state = { count: 0 } } render () { return ( <> <button onClick={() => { this.setState({ count: this.state.count + 1 }) }}>increment</button> <Iner count={ this.state.count } key={this.state.count} /> </> ) } }
这样的手段性能消耗较大也较为粗暴,但是确实可以在某些时刻强制组件更新。
请参阅关于
避免派生状态的博文
,以了解出现 state 依赖 props 的情况该如何处理。
2.static getDerivedStateFromProps
在初始化阶段中,getDerivedStateFromProps的作用基本只有一个,就是解决上面所说的问题,需要通过props来创建一个state并且建立联系,类似于Vue中computed。
getDerivedStateFromProps接收两个参数分别是prevProps和prevState,即更新前的属性和状态。要求返回一个对象来合并更新state,如果无需更新state,直接返回null即可,也可以偷偷设置几个新的状态。
class Iner extends Component { constructor (props) { super(props); this.state = { ownCount: props.count }; } static getDerivedStateFromProps (prevProps, prevState) { console.log(prevProps, prevState); return { ownCount: prevProps.count, someState: 1 }; } render () { return ( <p>{ this.state.ownCount }!</p> ) } } class Outer extends Component { constructor (props) { super(props); this.state = { count: 0 } } render () { return ( <> <button onClick={() => { this.setState({ count: this.state.count + 1 }) }}>increment</button> <Iner count={ this.state.count } /> </> ) } }
其实这也是不太好的操作,react推荐我们采用更合理简便的方式做到这一点,这将在运行中阶段在做详细的解释。
3. UNSAFE_componentWillMount
UNSAFE_componentWillMount()
在挂载之前被调用。它在
render()
之前调用,因此在此方法中同步调用
setState()
不会触发额外渲染(不要在初次render前setState)。通常,我们建议使用
constructor()
来初始化 state, 如果需要做派生状态的话,也有getDerivedStateFromProps来使用。
避免在此方法中引入任何副作用或订阅(render前保持纯净)。如遇此种情况,请改用
componentDidMount()
。
此方法是服务端渲染唯一会调用的生命周期函数。
也就是说,我们可以在这里对初始化设置的状态再利用setState做一次修改,纯纯无用功。而一些事件监听呀,初始化的接口调用之类的之前可以放在这里执行,但是为了保证DOM加载实际与接口调用事件不要冲突,也推荐大家放到render执行之后再进行,所以这个钩子就被完全废弃了。
class Iner extends Component { constructor (props) { super(props); this.state = { ownCount: props.count }; } static getDerivedStateFromProps (prevProps, prevState) { console.log('getDerivedStateFromProps', prevProps, prevState); return { ownCount: prevProps.count, a:1 }; } UNSAFE_componentWillMount () { this.setState({ ownCount: this.state.ownCount + 1 }); console.log('componentWillUnmount'); } render () { return ( <p>{ this.state.ownCount }!</p> ) } }
4.render
render()
方法是 class 组件中唯一必须实现的方法。
当
render
被调用时,它会检查
this.props
和
this.state
的变化并返回以下类型之一:
-
React 元素
。通常通过 JSX 创建。例如,
<div />
会被 React 渲染为 DOM 节点,
<MyComponent />
会被 React 渲染为自定义组件,无论是
<div />
还是
<MyComponent />
均为 React 元素。 -
数组或 fragments
。 使得 render 方法可以返回多个元素。欲了解更多详细信息,请参阅
fragments
文档。 -
Portals
。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关
portals
的文档 -
字符串或数值类型
。它们在 DOM 中会被渲染为文本节点 -
布尔类型或
null
。什么都不渲染。(主要用于支持返回
test && <Child />
的模式,其中 test 为布尔类型。)
render()
函数应该为纯函数,这意味着在不修改组件 state或者传入新props 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在
componentDidMount()
或其他生命周期方法中执行你的操作。保持
render()
为纯. 函数,可以使组件更容易维护。
总的来说,render函数是React组件渲染内容的基石,在初始化阶段和更新阶段都可以执行,需要注意的是,render函数中不应该承载其他的副作用的逻辑,保证纯函数的特性,同样的属性状态,就应该有同样渲染的结果。
5.componentDidMount
会在render之后执行, 并且可以访问到真实dom, 相当于vue中的mounted
-
可以进行初始化接口调用等副作用操作(计时器…)
-
可以访问到dom, 可以去实例化一些与dom有关的插件
二、运行中阶段
组件的
props
或
state
的改变都可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
-
componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
-
static getDerivedStateFromProps()
-
shouldComponentUpdate()
-
componentWillUpdate() / UNSAFE_componentWillUpdate()
-
render()
-
getSnapshotBeforeUpdate()
-
componentDidUpdate()
1.UNSAFE_componentWillReceiveProps
这个钩子目前也已经被废除,但是在之前应用还比较广泛,它会在传入最新属性之前触发,需要注意的是,状态更改的时候不会触发,此函数可以接受到最新的属性。
UNSAFE_componentWillReceiveProps()
会在已挂载的组件接收新的 props 之前被调用。如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较
this.props
和
nextProps
并在此方法中使用
this.setState()
执行 state 转换。
请注意,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。
在
挂载
过程中,React 不会针对初始 props 调用
UNSAFE_componentWillReceiveProps()
。组件只会在组件的 props 更新时调用此方法。调用
this.setState()
通常不会触发
UNSAFE_componentWillReceiveProps()
。
之前应用最广的就是,如果父组件传入的属性发生改变后,我们想要去相应的更新最新一下状态的时候触发, 因为此函数不会在setState之后执行,所以不用担心死循环发生,但是需要特别注意的是,只要父组件re-render,子组件都会认为接受到新属性了,哪怕属性值没有改变,所以在这里要做好判断。
大家可以发现,这个功能其实和getDerivedStateFromProps的作用一样,所以也被废弃了。
2.getDerivedStateFromProps
这个钩子在初始化阶段已经说过,它在运行中阶段也会跟随属性和状态的变化而触发,我们说了,它主要可以用来做计算属性这样的事情,也可以偷偷新建几个状态。状态更新即调用setState之后也会执行,所以也可以做一些新的计算。
但是这样不太好(性能损耗, 没有缓存),React推荐我们碰到这样的情况,采用下面几种方式来解决:
-
如果想执行副作用,放到render之后执行,保证DOM已经准备好做出响应(componentDidUpdate)。
-
如果想做一些计算,比如根据更新后的属性和状态,计算出真正使用的某些数据,那
请使用 memoization helper 代替
。 -
如果你想
在 prop 更改时“重置”某些 state
,请考虑使组件
完全受控
或
使用 key 使组件完全不受控
代替。
getDerivedStateFromProps能力强大, 可以接收到最新的属性和状态,然后可以生成新的状态, 并且当属性变化的时候, 生成的状态也会变化。
但是要一直的不断的去注意性能问题,比如属性和状态看似改变实则没有改变的情况,需要进行处理,不要再次进行计算
解决办法:
-
在getDerivedStateFromProps中生成新的状态来缓存当前传入的数据,下一次数据传入用于作对比判断,是否要进行计算;但是这样的话会使得getDerivedStateFromProps中逻辑太过于复杂
-
(推荐)使用memoization方式来处理, 不使用getDerivedStateFromProps来生成新的状态,而是每次用的时候都去重新“计算”, 将计算的方法利用memoize进行缓存,这样虽然看似每次都在计算,但是如果传入的参数是历史已经计算过的,那么就会从缓存中获取结果而不是重新计算。
memorization是指
使用PureComponent组件来代替Component,这样父组件属性更新后值相同的话不会触发子组件更新
使用一席耳工具库, 缓存每次计算的结果,那么就可以将这样的计算放置在render中了
完全受控是指子组件既然这样依赖父组件,那就别有自己的想法了,就做成一个轻量的UI组件来使用父组件传进来的属性就可以。
使用key完全不受控是指利用key强制刷新的能力,来使子组件强制更新后更新状态。
3.shouldComponentUpdate
根据
shouldComponentUpdate()
的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
当 props 或 state 发生变化时,
shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用
forceUpdate()
时不会调用该方法。
此方法仅作为
性能优化的方式
而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该
考虑使用内置的
PureComponent
组件
,而不是手动编写
shouldComponentUpdate()
。
PureComponent
会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
如果你一定要手动编写此函数,可以将
this.props
与
nextProps
以及
this.state
与
nextState
进行比较,并返回
false
以告知 React 可以跳过更新。请注意,返回
false
并不会阻止子组件在 state 更改时重新渲染。
我们不建议在
shouldComponentUpdate()
中进行深层比较或使用
JSON.stringify()
。这样非常影响效率,且会损害性能。
目前,如果
shouldComponentUpdate()
返回
false
,则不会调用
UNSAFE_componentWillUpdate()
,
render()
和
componentDidUpdate()
。后续版本,React 可能会将
shouldComponentUpdate
视为提示而不是严格的指令,并且,当返回
false
时,仍可能导致组件重新渲染。
4.UNSAFE_componentWillUpdate()
注意
此生命周期之前名为
componentWillUpdate
。该名称将继续使用至 React 17。可以使用
rename-unsafe-lifecycles codemod
自动更新你的组件。
当组件收到新的 props 或 state 时,会在渲染之前调用
UNSAFE_componentWillUpdate()
。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。
注意,你不能此方法中调用
this.setState()
;在
UNSAFE_componentWillUpdate()
返回之前,你也不应该执行任何其他操作(例如,dispatch Redux 的 action)触发对 React 组件的更新
通常,此方法可以替换为
componentDidUpdate()
。如果你在此方法中读取 DOM 信息(例如,为了保存滚动位置),则可以将此逻辑移至
getSnapshotBeforeUpdate()
中。
注意
如果
shouldComponentUpdate()
返回 false,则不会调用
UNSAFE_componentWillUpdate()
。
5.render
与运行中render一样,渲染最新的DOM,此时this.props和this.state已经是最新的了。
6.getSnapshotBeforeUpdate
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。接受的是上一次的属性和状态,它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。所以可以利用上一次的属性状态和更新后的属性状态作比较之后做出某些记录,作为返回值传递给
componentDidUpdate()以供处理
。
此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
应返回 snapshot 的值(或
null
)。
7.componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。
componentDidUpdate(prevProps) { // 典型用法(不要忘记比较 props): if (this.props.userID !== prevProps.userID) { this.fetchData(this.props.userID); } }
你也可以在
componentDidUpdate()
中
直接调用
setState()
,但请注意
它必须被包裹在一个条件语句里
,正如上述的例子那样进行处理,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。 欲了解更多有关内容,请参阅
为什么 props 复制给 state 会产生 bug
。
如果组件实现了
getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为
componentDidUpdate()
的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
注意
如果
shouldComponentUpdate()
返回值为 false,则不会调用
componentDidUpdate()
。
三、销毁阶段
当组件被销毁的时候,例如父组件通过条件渲染后不再渲染子组件的时候,此阶段执行,当组件卸载及销毁之前,会执行唯一的一个componentWillUnmount。
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在
componentDidMount()
中创建的订阅等。
componentWillUnmount()
中
不应调用
setState()
,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
react/vue中的key有什么作用
1.虚拟DOM中key的作用:
(1)简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
(2)详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,然后react进行新虚拟DOM与旧虚拟DOM的diff比较。
(3)强制刷新
2.用index作为key可能引发的问题
(1)若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面没问题,但是效率低
(2)如果结构中包含输入类的DOM,会产生错误DOM更新==>界面有问题
react的应用(基于脚手架)
1.全局安装脚手架
npm i -g create-react-app
2.创建项目
create-react-app 项目名
3.启动项目
首先进入项目文件夹
npm start
react脚手架项目结构
public----静态资源文件 favicon---网站页签图标 index.html---主页面 manifest.json---应用加壳的配置文件 robots---爬虫协议文件 src---源码文件 App.css--------App组件的样式 App.js---------App组件 App.test.js----用于给App做测试 index.css------样式 index.js-------入口文件
今天学习提问的问题
filename:"main_[hash].js"
哈希值是否变化的依据是根据文件内容是否变化。文件发生变化哈希值就会改变,文件不发生改变哈希值就不会改变。
什么叫做响应式?
数据改变页面也相应改变。
Vue的响应式原理?
1、通过getter收集依赖
什么是依赖?依赖可以理解为一个观察者对象,里面包含数据必须的属性(比如属性作用域对象)、方法(比如watch的回调)、字符串表达式(比如渲染函数)等,简单来说,观察者对象Watcher的主要作用就是数据与视图的粘合剂,通过发布-订阅模式,响应式的进行视图渲染。
为什么在getter中能够收集到依赖信息呢?首先,视图要想渲染数据,肯定要有对数据的取操作,取值时,会触发对数据的取操作,此时,可以将依赖的数据信息添加到属性的订阅者列表中,这样后续数据如果有变化,就可以从数据的订阅者列表中,依次执行对应的处理逻辑。
2、通过setter派发更新
setter是承接getter的作用的。在第一次数据渲染时,会通过getter,收集到属性的所有订阅者信息,在给属性赋值时,会触发属性的setter方法,在setter里,会遍历订阅者列表,根据订阅者的表达式,回调,处理对应的逻辑方法,实现数据的响应式更新。setter在响应式系统中,更多的可能是承接一个启后的角色。