React笔记2

一、组件state

1.概述

  • state必须是代表一个组件UI呈现的最小状态集
    没有任何多余的状态、没有计算而来的中间状态
  • state所代表的数据分为两类:
    • 用作渲染条件使用到的数据来源;
    • 用作组件UI展现形式的判断依据;
  • 判断一个变量是不是应该作为state,可以通过以下依据判断:
    • 这个变量是否是通过props从父组件中获取?如果是,它就不是一个状态;
    • 这个变量是否在组件整个生命周期中都保持不变?如果是,它就不是一个状态;
    • 这个变量是否可以通过其他state或props计算得到?如果是,它就不是一个状态;
    • 这个变量是否在组件的render方法中使用?如果不是,它就不是一个状态;

2.正确修改state

(1)、不能直接修改state

  • 直接修改state, 组件不会重新触发render,例如:
    this.state.title = ‘React’; // 错误
  • 正确的修改方式是setState(); 例如:
    this.setState({title: ‘React’});

(2)、state的更新是异步的

  • 调用setState时,组件的state并不会立即改变,setState只是把要修改的状态放入一个队列中,React会优化真正的执行时机,并且出于性能原因,可能会将多次setState的状态修改合并成一次状态修改。
  • 同样不能依赖当前的props计算下一个状态,因为props的更新也是异步的

更新状态应该注意的问题

  • 连续两次调用this.setState{quantity: this.state.quantity + 1}, 在React合并多次修改为一次的情况下,相当于等价执行了如下代码:
    Object.assign( previousState, {quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1})
    这时,后面的操作会覆盖前面的操作,最终quantity只增加了1。
  • 上述问题,可以使用setState的函数参数解决:
    setState可以接受一个函数参数,这个函数有两个参数,第一个是当前最新的状态(本次组件状态修改后生效后的状态)的前一个状态preState;第二个参数是当前最新的属性props。
    this.setState((preState, props) => ({quantity: preState.quantity + 1}))

(3)、state的更新是一个合并的过程

当调用setState修改组件的状态时,只需要传入发生改变的state,而不是组件完整的state.例如:
一个组件完整状态为:this.state = {
tittle : ‘React’,
content: ‘wonderful!’,
}
当只有title发生变化时,只需要将修改后的title传递给setState即可:
this.setState({title: ‘Reactjs’});
React会合并新的title到原来的组件状态中,同时保留原有的状态content,合并后的state为:
{
tittle : ‘Reactjs’,
content: ‘wonderful!’,
}

(4)、state与不可变对象

React官方建议把state当做不可变对象,原因是:

  • 因为对不可变对象的修改会返回一个新对象,不需要担心原有对象在不小心的情况下被修改导致的错误,方便程序管理和调试;
  • 出于性能考虑,当对象组件状态都是不可变对象时,在组件的shouldComponentUpdate方法中仅需要比较前后两次对象的引用就可以判断状态是否真的改变,从而避免不必要的render调用。

如何创建新的状态?根据状态的类型可以分成以下三种情况:

  • 1.状态的类型是不可变类型(数字、字符串、布尔值、null、undefined)
    这种情况,因为状态是不可变类型,所以直接给要修改的状态赋一个新值即可。
  • 2.状态的类型是数组:
    方法一:使用preState、concat/slice/filter创建新数组,例如(books是一个数组):
    this.setState(preState => ({
    books: preState.books.concat([‘React Guide’]);
    }))
    方法二:ES6 spread syntax
    this.setState(preState => ({
    books: […preState.books, ‘React Guide’];
    }))

注意:不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组基础上修改的,而concat、slice、filter会返回一个新的数组。

  • 状态的类型是普通对象(不包含字符串、数组)
    方法一:使用ES6的Object.assgin方法:
    this.setState(preState => ({
    owner: Object.assgin({}, preState.owner, {name: ‘Jason’});
    }))
    方法二:使用对象扩展语法(object spread properties):
    this.setState(preState => ({
    owner: {…preState.owner, name: ‘Jason’};
    }))

3.组件与服务器通信

(1)、组件挂载阶段通信

  • React官方推荐在componentDidMount中与服务器进行通信。这个时候组件已经挂载,真实DOM也已经渲染完成,是调用服务器API最安全的地方。
  • componentWillMount中执行服务器通信也是很常见的。componentWillMount执行时机比componentDidMount执行稍微靠前,时间差可以完全忽略不计。
  • componentDidMount是执行服务器通信的最佳时机的原因:
    (1)、componentDidMount通信,可以保证获取到服务器数据时,组件已经挂载,即使直接操作DOM也是安全的,而componentWillMount无法保证这一点;
    (2)、当组件在服务器端渲染时,componentWillMount会被调用两次,一次是在服务器端,一次是在浏览器端,而componentDidMount能保证在任何情况下只会被调用一次,从而不会发送多余的数据请求;

(2)、组件更新阶段通信

组件更新阶段与服务器通信,componentWillReceiveProps非常适合做这个工作。在这个方法中,当判断props中某个属性变化了,可以在调用后端接口获取数据。
需要注意的是:componentWillReceiveProps的执行并不能保证props一定发生了修改。

4.组件间通信

(1)、父子组件间的通信

父子组件间通过props属性进行通信:

  • 父组件向子组件传递数据:
    通过props将数据传递给子组件
  • 子组件向父组件传递数据:
    父组件通过props传递一个回调函数给子组件,子组件调用该回调函数,通过回调函数参数将数据传递给父组件。

(2)、兄弟组件间通信

当两个组件不是父子关系但是有相同的父组件时,称为兄弟组件;注意,这里的兄弟组件在整个组件树上并不一定处于同一层级。

通过状态提升的方式,可以实现兄弟组件间通信:
将兄弟组件共享的状态,提升保存到距离它们最近的共同父组件内,任何一个兄弟组件都可以父组件传递的回调函数来修改共同状态,父组件通过props向下传递给所有兄弟组件。(本质上还是父子组件间的通信方法props)

(3)、Context通信

过多使用context会让应用中的数据流变得混乱,不推荐使用。

React提供的context上下文,可以让任意层级的子组件都可以获取父组件中的状态和方法。
创建context的方式是:
(1)在提供context的组件内新增一个getChildContext方法,返回context对象,然后在组件的childContextType属性上定义context对象的属性的类型信息;例如:

//创建context对象,包含onAddUser方法
getChildContext() {
   return {onAddUser: this.handlerAddUser};
}
// 声明context的属性的类型信息
UserListContainer.childContextTypes = {
   onAddUser: PropTypes.func
};

(2)(context值会在组件树中自动向下传递)在子组件中使用时,需要先声明使用的context属性,再使用。例如:

handleClick() {
   this.context.onAddUser(this.state.newUser);
}
// 声明要使用的context对象的属性
UserAdd.contextTypes = {
   onAddUser: PropTypes.func
};

注意: 当context中包含数据时,修改context中的数据,一定不能直接修改,而是要通过setState修改,组件state的变化会创建一个新的context,然后重新传递给子组件

(4)、延伸通信方法

也可以使用消息队列来实现组件通信,可以引入EventEmitter或Postal.js等消息队列库来实现。甚至当应用更加复杂时,可以引入专门的状态管理库实现组件通信和组件状态的管理,例如Redux和MobX是当前流行的两个状态管理库。

5、特殊的ref

ref可以用来获取任意DOM元素,甚至可以获取React组件实例。在一些场景下,ref的使用可以带来便利,例如控制元素的焦点、文本的选择或者和第三方操作DOM的库集成。但绝大多数场景下,应该避免使用ref,因为它破坏了React中以props为数据传递介质的典型数据流。

(1)、在DOM元素上使用ref

ref接收一个回调函数作为值,在组件被挂载或卸载时,回调函数都会被调用,组件挂载时,回调函数会接收当前DOM元素作为参数;在组件卸载时,回调函数会接收null作为参数;
下面例子:可以使页面显示后,鼠标焦点自动聚焦到输入框:

class AutoFocusTextInput extends React.Component {
     componentDidMount() {
          this.textInput.focus(); // 通过ref让input自动获取焦点
     }
     render() {
          return (
                <div>
                      <input
                          type="text"
                          ref={(input) => { this.textInput = input; }}/>
                </div>
          );
     }
}

(2)、在组件上使用ref

React组件也可以定义ref,此时ref的回调函数接收的参数是当前组件的实例,这提供了一种在组件外操作组件的方式。
例如: AutoFocusTextInput组件的外部组件是Container,则可以在Container中通过ref直接调用AutoFocusTextInput中的方法blur。

class Container extends React.Component {
    constructor(pros) {
    	super(props);
    	this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.inputInstance.blur(); // 通过ref调用AutoFocusTextInput组件的方法
    }
    render() {
        return (
            <div>
                 <AutoFocusTextInput ref={(input) => {this.inputInstance = input }} />
                 <button onClick={this.handleClick}>失去焦点</button>
            </div>
        );
    }
}

注意:只能为类组件定义ref属性,而不能为函数组件定义ref属性,例如:如果上述代码中AutoFocusTextInput是一个函数组件
function AutoFocusTextInput() {

}
那么Container中的ref写法将不起作用;
函数组件虽然不能定义ref属性,但这并不影响在函数组件内部使用ref来引用其他DOM元素或组件

(3)、父组件访问子组件的DOM节点

在子组件的DOM元素上定义ref,ref的值是父组件传递给子组件的一个回调函数,这样父组件的回调函数中就能获得这个DOM元素。例如:

function Children(props) {
     // 子组件使用父组件传递的inputRef,为input的ref赋值
     return (
         <div>
             <input  ref={props.inputRef} />
         </div>
     );
}

class Parent extends React.Component {
     render() {
         // 自定义一个属性inputRef,值是一个函数
         return (
              <Children
                   inputRef={ el => this.inputElement = el}
              />
         );
     }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值