React-纯函数

纯函数

当我第一次听到 “纯函数 (Pure Function)” 这个术语的时候我很疑惑。常规的函数做错了什么?为什么要变纯? 为什么我需要纯的函数?

除非你已经知道什么是纯函数,否则你可能会问同样的疑惑。不过这个概念其实很简单。我们可以花个 5 分钟一起来看以下。

什么函数是纯的?

纯函数的定义是:

  1. 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
  2. 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。

这就是纯的函数。 如果一个函数符合上述 2 个要求,它就是纯函数。 你可能在过去甚至无意地情况下编写过纯函数。

在我们研究一个函数一个纯或不纯之前,让我们先讨论一下可怕的“副作用”。

什么是可观察的副作用?

一个可以被观察的副作用是在函数内部与其外部的任意交互。这可能是在函数内修改外部的变量,或者在函数里调用另外一个函数等。

注: 如果纯函数调用纯函数,则不产生副作用依旧是纯函数。

副作用来自,但不限于:

  • 进行一个 HTTP 请求
  • Mutating data
  • 输出数据到屏幕或者控制台
  • DOM 查询/操作
  • Math.random()
  • 获取的当前时间

副作用本身并不是毒药,某些时候往往是必需的。 但是,对于要保持纯粹的函数,它不能包含任何副作用。当然,并非所有函数都需要是纯函数。 我将在稍后讨论这个情况。

不过首先,让我们来看一些纯的和不纯的函数对比的例子……

纯函数的例子

以下是一个计算产品税后价格(英国税率是20%)的纯函数的例子:

function priceAfterTax(productPrice) { return (productPrice * 0.20) + productPrice;}

它符合我们所说的两条纯函数的定义。不依赖于任何外部输入,不改变任何外部数据、没有副作用。

即使你用同样的输入运行运行这个函数 100,000,000 次它依旧产生同样的结果

非纯函数

我们已经看了纯函数的例子,现在一起来看一个非纯函数(Impure function)的 JavaScript 例子:

var tax = 20;
function calculateTax(productPrice) {
    return (productPrice * (tax/100)) + productPrice;
}

暂停片刻,看看你是否能看出为什么这个函数不纯。

其中函数的计算结果取决于外部 tax 变量,而纯函数不能依赖外部变量。它没有满足定义中的第一个要求,因此这个函数是不纯的。

为什么说纯函数在 JavaScript 很重要?

纯函数在函数式编程中被大量使用。而且诸如 ReactJSRedux 等优质的库都需要使用纯函数。

不过,纯函数也可以用在平常的 JavaScript 开发中使用,不一定要限死在某个编程范例中。 你可以混合纯的和不纯的函数,这完全没问题。

并非所有函数都需要是纯的。 例如,操作 DOM 的按钮按下的事件处理程序就不适合纯函数。 不过,这种事件处理函数可以调用其他纯函数来处理,以此减少项目中不纯函数的数量。

可测试性和重构

另一个使用纯函数的原因是测试以及重构。

使用纯函数的一个主要好处是它们可以直接测。 如果传入相同的参数,它们将始终产生相同的结果。

同时纯函数还使得维护和重构代码变得更加容易。你可以放心地重构一个纯函数,不必操心没注意到的副作用搞乱了整个应用而导致终调试地狱。(译注:如果项目中充斥着副作用,那么函数/模块之间的逻辑可能互相交织耦合,在后期新增逻辑时可能由于依赖复杂而难以重构,更常见的是开发为了应付需求而不断的引入新的副作用到原本的逻辑上从而导致代码变得越来越糟糕。)

正确地使用纯函数可以产生更加高质量的代码。并且也是一种更加干净的编码方式。

此外,纯函数不不是 JavaScript 的专利。想要了解更多内容可以参见 Wiki。同时也推荐阅读 开发建议手册 以及 纯函数 vs. 非纯函数.

React-react-router路由

react-router

React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

安装

npm i --save react-router-dom

引入

import {HashRouter as Router,Route,Redirect,Switch,Link} from 'react-router-dom'

定义路由及重定向

const MyRouter = ()=>(
    <Router>
        <Switch>
            <Route path='/home' component={Home}></Route>
             <Route path='/user' component={User}></Route>
             <Redirect from='/' to='/home' exact ></Redirect>
        </Switch>
    </Router>
)

Redirect

注意事项:exact 精确匹配 (Redirect 即使使用了exact, 外面还要嵌套Switch 来用)

HashRouter

注意事项:Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack,这个警告只有在hash 模式会出现。在NavLink 加上 replace 来解决

嵌套路由

const MyRouter = ()=>(
    <Router>
        <Switch>
            <Route path='/home' component={Home}></Route>
             <Route path='/user' component={User}></Route>
             <Route path='/right' render={()=>(
                    <Right>
                        <Route path='/right/roles' component={Roles}></Route>
                        <Route path='/right/find' component={Find}></Route>
                    </Right>
                )}</Route>
             <Redirect from='/' to='/home' exact ></Redirect>
        </Switch>
    </Router>
)

路由跳转方式

声明式导航

import React,{Component} from 'react'
import {NavLink} from 'react-router-dom'
export default class Right extends Component {
    render () {
        return (
            <div>
                <ul>
                    <li>
                        <NavLink to='/right/roles'>roles</NavLink>
                    </li>
                    <li>
                        <NavLink to='/right/find'>find</NavLink>
                    </li>
                </ul>
            </div>
        )
    }
}

编程式导航

handleClick = (data)=>{
    this.props.history.push(`/right/preview/${data}`)
}
<Route path='/home/:id'></Route>
<div>{this.props.match.params.id}</div>

路由匹配原理

路由拥有三个属性来决定是否“匹配“一个 URL:

  1. 嵌套关系
  2. 它的 路径语法
  3. 它的 优先级

嵌套关系

React Router 使用路由嵌套的概念来让你定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。

路径语法

路由路径是匹配一个(或一部分)URL 的 一个字符串模式。大部分的路由路径都可以直接按照字面量理解,除了以下几个特殊的符号:

  • :paramName – 匹配一段位于 /?# 之后的 URL。 命中的部分将被作为一个参数
  • () – 在它内部的内容被认为是可选的
  • * – 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数
<Route path="/hello/:name">         // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)">       // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*">           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

如果一个路由使用了相对路径,那么完整的路径将由它的所有祖先节点的路径和自身指定的相对路径拼接而成。使用绝对路径可以使路由匹配行为忽略嵌套关系。

优先级

最后,路由算法会根据定义的顺序自顶向下匹配路由。因此,当你拥有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的路径。例如,千万不要这么做:

<Route path="/comments" ... />
<Redirect from="/comments" ... />

React-slot插槽

slot

插槽的应用场景大致分为2种:

子组件通信-this.props.children

插槽可以很好地把按钮的事件处理函数留在父组件,但是插槽又可以将按钮插入子组件,非常方便的实现了2个子组件之间的通信

import React,{Component} from 'react'

class Navbar extends Component {
    render () {
        return (
            <div>
                {this.props.children}
            </div>
        )
    }
}

class Sidebar extends Component {
    render () {
        return (
            <div>
                Sidebar
            </div>
        )
    }
}

export default class App extends Component {
    state= {
        isShow:true
    }
    render () {
        return (
            <div>
                <Navbar>
                    <button onClick={()=>{
                            this.setState({
                                isShow:!this.state.isShow
                            })
                        }}}></button>
                </Navbar>
                {
                    this.state.isShow?<Sidebar/>:null
                }
            </div>
        )
    }
}

嵌套路由-this.props.children

插槽可以很好地时间路由配置中,在父路由js中通过{this.props.children}实现子路由的插入

router.js

import React,{Component} from 'react'
import {HashRouter as Router,Route,Redirect,Switch} from 'react-router-dom'
import Home from '....'
import Homelist from '....'
import Homenav from '....'
export default class MyRouter extends Component {
    render () {
        return (
            <Router>
                <Swtich>
                    <Route path='/home' render={()=>(
                        <Home>
                              <Route path='/home/list' compoennt={Homelist}></Route>
                             <Route path='/home/nav' compoennt={Homenav}></Route>
                        </Home>
                    )}></Route>
                </Swtich>
            </Router>
        )
    }
}

Home.js

import React,{Component} from 'react'
export default class Home extends Component {
    render () {
        return (
            <div>
                Home
                {this.props.children} {//通过this.props.children即可引入路由中定义的component={}中的组件}
            </div>
        )
    }
}

React-性能优化方案

性能优化方案

shouldComponentUpdate

作为一个性能调优函数,控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化

PureComponent

PureComponent按以下流程进行操作:

  1. 比较新props跟旧的props
  2. 比较新的state和老的state(值相等,或者对象含有相同的属性、且属性值相等 )
  3. 决定shouldcomponentUpdate 返回true或者false,
  4. 从而决定要不要呼叫 render function。

注意:如果你的 state 或 props 『永远都会变』,那 PureComponent 并不会比较快,因为 shallowEqual 也需要花时间。

React-pureComponent

pureComponent

React15.3中新加了一个 PureComponent 类,顾名思义, pure 是纯的意思,PureComponent 也就是纯组件,取代其前身 PureRenderMixin ,PureComponent 是优化 React 应用程序最重要的方法之一,易于实施,只要把继承类从 Component 换成 PureComponent 即可,可以减少不必要的 render 操作的次数,从而提高性能,而且可以少写 shouldComponentUpdate 函数,节省了点代码。

原理

当组件更新时,如果组件的 propsstate 都没发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。具体就是 React 自动帮我们做了一层浅比较:

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps)
  || !shallowEqual(inst.state, nextState);
}

shallowEqual 又做了什么呢?会比较 Object.keys(state | props) 的长度是否一致,每一个 key是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。

使用指南

易变数据不能使用一个引用

案例:

class App extends PureComponent {
  state = {
    items: [1, 2, 3]
  }
  handleClick = () => {
    const { items } = this.state;
    items.pop();
    this.setState({ items });
  }
  render() {
    return (< div>
      < ul>
        {this.state.items.map(i => < li key={i}>{i}< /li>)}
      < /ul>
      < button onClick={this.handleClick}>delete< /button>
    < /div>)
  }
}

会发现,无论怎么点 delete 按钮,li都不会变少,因为用的是一个引用,shallowEqual 的结果为 true。改正:

handleClick = () => {
  const { items } = this.state;
  items.pop();
  this.setState({ items: [].concat(items) });
}

这样每次改变都会产生一个新的数组,也就可以 render 了。这里有一个矛盾的地方,如果没有 items.pop(); 操作,每次 items 数据并没有变,但还是 render 了,这不就很操蛋么?呵呵,数据都不变,你 setState 干嘛?

不变数据使用一个引用

函数属性

我们在给组件传一个函数的时候,有时候总喜欢:

< MyInput onChange={e => this.props.update(e.target.value)} />
//或者
update(e) {
  this.props.update(e.target.value)
}
render() {
  return < MyInput onChange={this.update.bind(this)} />
}

由于每次 render 操作 MyInput 组件的 onChange 属性都会返回一个新的函数,由于引用不一样,所以父组件的 render 也会导致 MyInput 组件的 render,即使没有任何改动,所以需要尽量避免这样的写法,最好这样写:

update = (e) => {
  this.props.update(e.target.value)
}
render() {
  return < MyInput onChange={this.update} />
}

空对象或空数组

有时候后台返回的数据中,数组长度为0或者对象没有属性会直接给一个 null,这时候我们需要做一些容错:

class App extends PureComponent {
  state = {
    items: [{ name: 'test1' }, null, { name: 'test3'  }]
  }
  store = (id, value) => {
    const { items } = this.state;
    items[id]  = assign({}, items[id], { name: value });
    this.setState({ items: [].concat(items) });
  }
  render() {
    return (< div>
      < ul>
        {this.state.items.map((i, k) =>
          < Item store={this.store} key={k} id={k} data={i || {}} />)
        }
      < /ul>
    < /div>)
  }
}

当某一个子组件调用 store 函数改变了自己的那条属性,触发 render 操作,如果数据是 null 的话 data 属性每次都是一个 {}{} ==== {}false 的,这样无端的让这几个子组件重新 render 了。

最好设置一个 defaultValue{},如下:

defaultValue = {}
< Item store={this.store} key={k} id={k} data={i || defaultValue} />

复杂状态与简单状态不要共用一个组件

这点可能和PureComponent没多少关系,但做的不好可能会浪费很多性能,比如一个页面上面一部分是一个复杂的列表,下面是一个输入框,抽象代码:

change = (e) => {
  this.setState({ value: e.target.value });
}
render() {
  return (< div>
    < ul>
      {this.state.items.map((i, k) => < li key={k}> {...}< /li>)}
    < /ul>
    < input value={this.state.value} onChange={this.change} />
  < /div>)
}

表单和列表其实是没有什么关联的,表单的值也可能经常变动,但它的会给列表也带来必然的diff操作,这是没必要的,最好是给列表抽出成一个单独的 PureComponent 组件,这样 state.items 不变的话,列表就不会重新 render 了。

shouldComponentUpdate共存

如果 PureComponent 里有 shouldComponentUpdate 函数的话,直接使用 shouldComponentUpdate 的结果作为是否更新的依据,没有shouldComponentUpdate 函数的话,才会去判断是不是 PureComponent ,是的话再去做 shallowEqual浅比较。

// 这个变量用来控制组件是否需要更新
var shouldUpdate = true;
// inst 是组件实例
if (inst.shouldComponentUpdate) {
  shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
} else {
  if (this._compositeType === CompositeType.PureClass) {
    shouldUpdate = !shallowEqual(prevProps, nextProps) ||
      !shallowEqual(inst.state, nextState);
  }
}

老版本兼容写法

import React { PureComponent, Component } from 'react';
class Foo extends (PureComponent || Component) {
  //...
}

这样在老版本的 React 里也不会挂掉。

总结

PureComponent真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,反正shallowEqual那一关就过不了,不过记得 propsstate 不能使用同一个引用哦。

React-生命周期

生命周期

53884612-fa2b0800-4056-11e9-879f-050e0b33e0e9.png?ynotemdtimestamp=1551878671356

初始化阶段

render

只能访问this.props和this.state,不允许修改状态和DOM输出

componentWillMount

render之前最后一次修改状态的机会

在ssr中 这个方法将会被多次调用,所以会重复触发多少遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏,变得不够安全高效逐步废弃。

因此需要加上UNSAFE

UNSAFE_componentWillMount () {

}

componentDidMount

成功render并渲染完成真实DOM之后触发,可以修改DOM

componentWillMount () {

}

运行中阶段

componentWillReceiveProps

外部组件多次频繁更新传入多次不同的props,会导致不必要的异步请求

触发

UNSAFE_componentWillReceiveProps(nextProps) 在组件接收到新的参数时被触发,当父组件导致子组件更新的时候, 即使接收的 props 并没有变化, 这个函数也会被调用.

工作方式

参数是组件接收到的新的 props , 用于比对新的 props 和原有的 props, 用户需要在函数体中调用 setState() 来更新组件的数据.

UNSAFE_componentWillReceiveProps(nextProps){
    if (this.props.currentExercise.id !== nextProps.currentExercise.id){
        this.setState({...nextProps.currentExercise})
    }
}

shouldComponentUpdate

  1. setState()函数在任何情况下都会导致组件重渲染吗?如果setState()中参数还是原来没有发生任何变化的state呢?

  2. 如果组件的state没有变化,并且从父组件接受的props也没有变化,那它就一定不会重渲染吗?

  3. 如果1,2两种情况下都会导致重渲染,我们该如何避免这种冗余的操作,从而优化性能?

没有导致state的值发生变化的setState是否会导致重渲染 ——【会!】

shouldComponentUpdate是一个性能调优函数,是重渲染时render()函数调用前被调用的函数,它接受两个参数:nextProps和nextState,分别表示下一个props和下一个state的值。并且,当函数返回false时候,阻止接下来的render()函数的调用,阻止组件重渲染,而返回true时,组件照常重渲染。

nextProps:修改后的属性

nextState:修改后的状态

可以根据状态和参数中的状态的对比,返回true/false来判断是否需要更新

shouldComponentUpdate (nextProps,nextState) {
    return !(this.state.myname === nextState.myname) ||!(this.state.myage === nextState.myage) 
}

componentWillUpdate

更新前记录DOM 状态, 可能会做一些处理,与componentDidUpdate相隔时间如果过长,会导致状态不太信

import React, { Component } from 'react'

export default class App extends Component {
  state= {
    myname:'retr0'
  }
  UNSAFE_componentWillUpdate () {
    console.log('componentWillUpdate');
  }
  render() {
    return (
      <div>
        {this.state.myname}
        <button onClick={()=>{
          this.setState({
            myname:'xiaoming'
          })
        }}>Click</button>
      </div>
    )
  }
}

componentDidUpdate

import React, { Component } from 'react'

export default class App extends Component {
  state= {
    myname:'retr0'
  }
  componentDidUpdate () {
    console.log('componentDidUpdate');
  }
  render() {
    return (
      <div>
        {this.state.myname}
        <button onClick={()=>{
          this.setState({
            myname:'xiaoming'
          })
        }}>Click</button>
      </div>
    )
  }
}

销毁阶段

componentWillUnMount

用于清除计时器,window.scroll=null等操作

import React, { Component } from 'react'
class Navar extends Component {
  render () {
    return (
      <div style={{background:'#f99'}}>
         navbar-<button onClick={this.handleClick}>Click</button>
      </div>
    )
  }
  handleClick = ()=>{
    this.props.onEvent();
  }
}
class Slidebar extends Component {
  render () {
    return (
      <div style={{background:'#99f'}}>
        slidebar
        <ul>
          <li>11111</li>
          <li>22222</li>
          <li>33333</li>
        </ul>
      </div>
    )
  }
  //销毁声明周期
  componentWillUnmount () {
    console.log('componentWillUnmount');
  }
}
export default class 子传父 extends Component {
  state = {
    isShow:true
  }
  render() {
    return (
      <div>
        App
        <Navar onEvent={()=>{
          this.setState({
            isShow : !this.state.isShow
          })
        }}/>
        {this.state.isShow?<Slidebar/>:''}
      </div>
    )
  }
}

新增

static getDerivedStateFromProps

getDerivedStateFromProps 是一个静态方法, 是一个和组件自身”不相关”的角色. 在这个静态方法中, 除了两个默认的位置参数 nextProps 和 currentState 以外, 你无法访问任何组件上的数据。第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子) ,返回一个对象作为新的state,返回null则说明不需要在这里更新state

会被频繁地触发

无论是组件调用了 setState(), 接收的 props 发生了变化, 或是父组件的更新都会导致子组件上的 getDerivedStateFromProps被触发.

使用的时候必须非常小心

由于 getDerivedStateFromProps 会在 setState() 后被调用, 并且它的返回值会被用于更新数据. 这意味着你会在 setState() 之后触发 getDerivedStateFromProps, 然后可能意外地再次 “setState()”.

getDerivedStateFromProps(nextProps) 函数中的第一个位置参数未必是 “新” 的 props. 在组件内调用了 setState() 时, getDerivedStateFromProps 会被调用. 但是此时的组件其实并没有获得 “新” 的 props, 是的, 这个 nextProps 的值和原来的 props 是一样的.

这就导致了我们在使用 getDerivedStateFromProps 时, 必须添加很多逻辑判断语句来处理 props 上的更新和 state 上的更新, 避免意外地返回了一个 Updater 再次更新数据, 导致数据异常.

import React, { Component } from 'react'

class Child extends Component {
  state={
    ajax:''
  }
  // UNSAFE_componentWillReceiveProps () {
  //   console.log('componentWillReceiveProps');
  // }
  componentDidUpdate () {
    console.log(this.state.ajax);
  }
  static getDerivedStateFromProps (nextProps,currentState){
    return {
      ajax:nextProps.ajax
    }
  }
  render () {
    return (
      <div>
        child - {this.props.ajax}
      </div>
    )
  }
}

export default class App extends Component {
  state= {
    ajax:9000
  }
  render() {
    return (
      <div>
        <Child ajax={this.state.ajax}/>
        <button onClick={()=>{
          this.setState({
            ajax:1000
          })
        }}>Click</button>
      </div>
    )
  }
}

更优雅的做法

React 官方博客中提供了以下几种方案:

  1. 让表单控件变成完全受控组件, 不论是 onChange 处理函数还是 value 都由父组件控制, 这样用户无需再考虑这个组件 props 的变化和 state 的更新.
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
  1. 让表单控件变成完全不受控组件, 但是具有 key 属性.
    仍然用自身的数据来控制 value. 但是接收 props 中的某个字段作为 key 属性的值, 以此响应 props 的更新: 当 key 的值变化时 React 会替换 (reset)组件, 从而重新生成初始化数据.

When a key changes, React will create a new component instance rather than update the current one.

示例代码:

//组件内的代码
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}
// 在父组件中接收 props 中的数据作为 key
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>
  • 其他方法请参考 这里

getSnapshotBeforeUpdate

取代了 componetWillUpdate ,触发时间为update发生的时候,在render之后,dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。

import React, { Component } from 'react'

export default class App extends Component {
  state = {
    mytext :'retr0',
  }
  // componentWillUpdate () {
  //   console.log(this.state.mytext);
  // }
  getSnapshotBeforeUpdate () {
    console.log(this.state.mytext,'获取滚动条的位置');
    return {
      y:100
    }
  }
  componentDidUpdate(prevProps, prevState,data) {
    console.log(prevProps,prevState,data);
  }
  
  render() {
    console.log('render');
    return (
      <div>
        {this.state.mytext}
        <button onClick={()=>{
          this.setState({
            mytext:'xiaoming'
          })
        }}>Click</button>
      </div>
    )
  }
}

React-Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

优缺点

优点

跨组件访问数据

缺点

react组件树种某个上级组件shouldComponetUpdate返回false,当 context更新时,不会引起下级组件更新

React.createContext

const MyContext = React.createContext(defaultValue);

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

Context.Provider

<MyContext.Provider value={/* 某个值 */}>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。

Context.Consumer

注意:Context.Consumer内必须是回调函数,通过context方法改变根组件状态

<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。

这需要函数作为子元素(function as a child)这种做法。这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue

实例

import React,{Component} from 'react'
const myContext = React.createContext()

class Child extends Component{
  UNSAFE_componentWillUpdate (){
    console.log('componentWillUpdate1');
  }
  componentDidUpdate () {
    console.log('componentDidUpdate1');
  }
  render () {
    return (
      <myContext.Consumer>
        {
          context=>(
            <div>
              child -- {context.myname} -- {context.age}  -- {context.sex}
              <button onClick={this.handleClick.bind(this,context)}>change</button>
            </div>
          )
        }
      </myContext.Consumer>
    )
  }
  handleClick = (context)=>{
    context.changeState('xiaoming')
  }
}

export default class App extends Component {
  state={
    myname:'retr0'
  }
  UNSAFE_componentWillUpdate (){
    console.log('componentWillUpdate');
  }
  componentDidUpdate () {
    console.log('componentDidUpdate');
  }
  shouldComponentUpdate (nextProps,nextState) {
    return !(this.state.myname===nextState.myname)
  }
  changeState = (data)=>{
    this.setState({
      myname:data
    })
  }
  render () {
    return (
      <myContext.Provider value={{
        myname:this.state.myname,
        age:12,
        sex:1,
        changeState:this.changeState
      }}>
         <div>
          app
          <Child></Child>
        </div>
      </myContext.Provider>
    )
  }
}

React-ref标记 (父组件拿到子组件的引用,从而调用子组件的方法)

ref通信

ref标记 (父组件拿到子组件的引用,从而调用子组件的方法)

import React,{Component} from 'react'

class Navbar extends Component {
        state = {
            mytext = ''
        }
        reset= ()=>{
            this.setState({
                mytext:''
            })
        }
        render () {
            return (
                <div>
                    <input type='text' onChange={this.saveText}/>
                </div>
            )
        }
        saveText= (eve)=>{
            this.setState({
                mytext:eve.target.value
        })
    }
}

export default class App extends Component {
    render () {
        return (
            <div>
                 {//建立子组件ref的引用}
                <Navbar ref='text'/> 
              
                <button onClick={this.handleClick}>add</button>
            </div>
        )
    }
    handleClick = ()=>{
        console.log(this.refs.text.state.mytext){//查询子组件的state}
        this.refs.text.reset(){//调用子组件的方法}
    }
}

React-onEvent通过调用父的函数,实现由子组件控制父组件状态的变化

onEvent

import React,{Component} from 'react'

class Navbar extends Component {
    render () {
        return (
            <div>
                <button onClick={this.handleClick}>add</button>
            </div>
        )
    }
    handleClick = ()=>{
        this.props.onEvent() //通过调用父的函数,实现由子组件控制父组件状态的变化
    }
}

class Slidebar extends Component {
    render () {
        return (
            <div>
                <ul>
                    <li>1111</li>
                    <li>2222</li>
                    <li>3333</li>
                </ul>
            </div>
        )
    }
}

export default class App extends Component {
    state = {
        isShow:true
    }
    render () {
        return (
            <div>
                <Navbar onEvent={()=>{
                        this.setState({
                            isShow:!this.state.isShow
                        })
                    }}/>
                {
                    this.state.isShow?<Slidebar></Slidebar>:null
                }
            </div>
        )
    }
}