核心概念
生命周期
(旧)--16
初始化阶段
由ReactDOM.render()触发--初次渲染
- constructor
- componentWillMount:render之前最后一次修改状态的机会
- render:只能访问this.props和this.state,不允许修改状态和DOM输出
- componentDidMount:组件装载之后调用,此时可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在
componentWillUnmount中取消订阅
更新阶段
由组件内部this.setState()或父组件重新render触发
- componentWillReceiveProps:父组件修改属性触发改钩子
- shouldComponentUpdate:返回false会阻止render调用
- componentWillUpdate:不能修改属性和状态
- render:只能访问this.props和this.state,不允许修改状态和DOM输出
- componentDidUpdate:在组件重新渲染后调用该方法。
componentDidUpdate(prevProps, prevState, snapshot),该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至getSnapshotBeforeUpdate,然后在componentDidUpdate中统一触发回调或更新状态。
卸载阶段
由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount:当组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作
class Count extends React.Component {
// 构造器
constructor(props) {
console.log('Count-constructor')
super(props)
// 初始化状态
this.state = {
count: 0
}
}
// +1按钮的回调
add = () => {
// 获取原状态
const { count } = this.state
// 更新状态
this.setState({ count: count + 1 })
}
// 卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮的回调
force = () => {
this.forceUpdate()
}
// 组件将要挂载的钩子
componentWillMount() {
console.log('Count--componentWillMount')
}
// 组件挂载完毕的钩子
componentDidMount() {
console.log('Count--componentDidMount')
}
// 组件将要卸载的钩子
componentWillUnmount() {
console.log('Count--componentWillUnmount')
}
// 控制组件更新的阀门
shouldComponentUpdate() {
console.log('Count--shouldComponentUpdate')
return true
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('Count--componentWillUpdate')
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('Count--componentDidUpdate')
}
render() {
console.log('Count--render')
const { count } = this.state
return (
<div>
<h2>当前求和为{ count }</h2>
<button onClick={ this.add }>点击+1</button>
<button onClick={ this.death }>卸载组件</button>
<button onClick={ this.force }>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
// 父组件A
class A extends React.Component {
// 初始化状态
state = { carName: '奔驰' }
changeCar = () => {
this.setState({ carName: '奥迪' })
}
render() {
return(
<div>
<div>A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName} />
</div>
)
}
}
// 子组件B
class B extends React.Component {
// 第一次不算---组件将要接收新的props的钩子
componentWillReceiveProps(props) {
console.log('B --componentWillReceiveProps', props)
}
// 控制组件更新的阀门
shouldComponentUpdate() {
console.log('B--shouldComponentUpdate')
return true
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('B--componentWillUpdate')
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('B--componentDidUpdate')
}
render() {
console.log('B--render')
return(
<div>B组件,接收到的车是:{this.props.carName}</div>
)
}
}
ReactDOM.render(<A />, document.getElementById('test'))
新--17
初始化阶段
- constructor()
getDerivedStateFromProps- render()
- componentDidMount()
更新阶段
getDerivedStateFromProps- shouldComponentUpdate()
- render()
getSnapshotBeforeUpdate- componentDidUpdate()
卸载组件
- componentWillUnmount()
组件通信
react组件之间的通信常见的方式:
- 父向子通信
- 子向父通信
- 跨级组件通信
- 非嵌套关系的组件通信
- 父向子通信 父组件通过 props 向子组件传递需要的信息。父传子是在父组件中直接绑定一个正常的属性,这个属性就是指具体的值,在子组件中,用props就可以获取到这个值。
// 子组件
const Child = props =>{ return <p>{props.name}</p> }
// 父组件
Parent const Parent = ()=>{ return <Child name="父向子通信"></Child> }
- 子向父通信 props+回调的方式,使用公共组件进行状态提升。子传父是先在父组件上绑定属性设置为一个函数,当子组件需要给父组件传值的时候,则通过props调用该函数将参数传入到该函数当中,此时就可以在父组件中的函数中接收到该参数了,这个参数则为子组件传过来的值。
// 子组件 Child
const Child = props => {
const cs = msg => {
return () => {
props.callback(msg)
}
}
return (
<button onClick={cs('子向父通信')}>点击</button>
)
}
// 父组件 Parent
class Parent extends Component {
callback(msg) {
console.log(msg)
}
render() {
return <Child callback={this.callback.bind(this)}/>
}
}
- 跨级组件通信 如父组件向子组件的子组件或更深层次的子组件进行通信
-
使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。
-
使用context,context相当于一个大容器,我们可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。
// context方式实现跨级组件通信
// Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据
const BatteryContext = createContext();
// 子组件的子组件
class GrandChild extends Component {
render(){
return (
<BatteryContext.Consumer>
{
color => <h1 style={{"color":color}}>我是红色的:{color}</h1>
}
</BatteryContext.Consumer>
)
}
}
// 子组件
const Child = () =>{
return (
<GrandChild/>
)
}
// 父组件
class Parent extends Component {
state = {
color:"red"
}
render(){
const {color} = this.state
return (
<BatteryContext.Provider value={color}>
<Child></Child>
</BatteryContext.Provider>
)
}
}
- 非嵌套关系的组件通信 即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
- 可以使用自定义事件通信(发布订阅模式),使用pubsub-js
- 可以通过redux等进行全局状态管理
- 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
- 也可以new一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE的eventBus 就是发布订阅模式,是可以在React中使用的;
受控组件、非受控组件
受控组件
- HTML中的表单元素是可输入的,也就是有自己的可变状态,而React中可变状态通常保存在state中,并且只能通过
setState()方法来修改 - React讲state与表单元素值value绑定在一起,有state的值来控制表单元素的值
- 受控组件:
值受到react控制的表单元素
// 页面中所有输入类的DOM是随着输入维护状态--受控组件
class Login extends React.Component{
// 初始化状态
state = {
username: '',
password: ''
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是${username},密码是${password}`)
}
// 保存用户名到状态中
saveUsername = (event) => {
console.log(event.target.value)
this.setState({ username: event.target.value })
}
savePassword = (event) => {
this.setState({ password: event.target.value })
}
render() {
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username" />
密码:<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
非受控组件
- 调用
React.createRef()方法创建ref对象,将创建好的ref对象添加到文本框中,通过ref对象获取文本框的值 - 非受控组件:
表单组件没有 value prop就可以称为非受控组件
// 页面中所有输入类的DOM是现用现取--非受控组件
class Login extends React.Component{
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this
alert(`你输入的用户名是${username.value},密码是${password.value}`)
}
render() {
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input ref={ c => this.username = c } type="text" name="username" />
密码:<input ref={ c => this.password = c } type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
高阶组件
高阶函数与科里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
2、若A函数,调用的返回值依然是一个函数,那么A函数就可以称之为高阶函数
常见的高阶函数:Promise、setTimeout、数组方法(arr.map()等等)
函数科里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
class Login extends React.Component{
// 初始化状态
state = {
username: '',
password: ''
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是${username},密码是${password}`)
}
// 保存参数到状态中
saveFormData = (dataType) => {
return (event) => {
/*
let a = 'name'
let obj = {} // { name: 'Tome' }
obj[a] = 'Tom'
*/
this.setState({ [dataType]: event.target.value })
}
}
render() {
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
不用科里化实现上述功能:
class Login extends React.Component{
// 初始化状态
state = {
username: '',
password: ''
}
// 表单提交的回调
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`你输入的用户名是${username},密码是${password}`)
}
// 保存参数到状态中
saveFormData = (dataType,event) => {
this.setState({ [dataType]: event.target.value })
}
render() {
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username" />
密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
diff算法
1、react/vue中key有什么作用?(key的内部原理是什么?)
虚拟DOM中key的作用:
1)、简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
2)、详细的说:当状态中的数据发生变化的时候,react会根据【新数据】生成【新的虚拟DOM】,随后react进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a、旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没有变,直接使用之前的真实DOM;
若虚拟DOM中的内容变了,则生成新的虚拟DOM,随后替换掉页面中之前的真实DOM。
b、旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到页面
2、为什么遍历列表时,key最好不要用index?
用index作为key可能会引发的问题:
1)、若对数据进行:逆序添加、逆序删除等破坏顺序操作
会产生没有必要的真实DOM更新 ==> 界面效果没有问题,但效率低
2)、如果结果中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题
3、开发中如何选择key?
1)、最好使用每条数据的唯一标识作为key,比如:id、手机号、身份证号、学号等唯一值
2)、如果确定只是简单的展示数据,用index也是可以的