React专题:不可变属性

本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出javascript

来个人 GitHub repo 阅读完整的专题文章vue

来个人 我的博客 得到无与伦比的阅读体验java

React是用来解决状态同步的,但它却有一个与this.state并驾齐驱的概念。react

这就是this.propsgit

this.props是组件之间沟通的一个接口。github

原则上来说,它只能从父组件流向子组件,可是开发者有各类hack技巧,基本上近亲之间沟通是不成问题的。编程

this.props

this.props是一个极其简单的接口。世界须要更多这样的傻瓜接口

你只须要像写HTML标签的属性同样,把它写上去,它就传到了子组件的this.props里面。redux

不过有几个地方须要注意:框架

  • 有两个特殊的属性refkey,它们各有用途,并不会传给子组件的this.props
  • 若是只给属性不给值,React会默认解析成布尔值true
  • 除了字符串,其余值都要用花括号包裹。
  • 若是你把属性给了标签而不是子组件,React并不会解析。
import React, { Component, createRef } from 'react';
import Child from './Child';

class App extends Component {
    isPopular = false;
    refNode = createRef();
    
    render() {
        return [
            <Child key="react" ref={this.refNode} isPopular />,
            <Child key="vue" url="https://github.com/vuejs/vue" star={96500} />,
            <Child key="angular" owner="google" isPopular={this.isPopular} />,
        ];
    }
}

export default App;
复制代码

this.props是一个不可变对象

React具备浓重的函数式编程的思想。ide

提到函数式编程就要提一个概念:纯函数。

纯函数有几个特色:

  • 给定相同的输入,老是返回相同的输出。
  • 过程没有反作用。
  • 不依赖外部状态。
function doSomething(a, b) {
    return a + b;
}
复制代码

这是一种编程思想。若是你对这个概念有点模糊,我能够举个例子:

你的杀父仇人十年后忽然现身,因而你决定雇佣一个冷面杀手去解决他。

你会找一个什么样的杀手呢?

  • 给多少钱办多少事,效果可预期,从不失手。
  • 不误伤百姓,不引发动静。
  • 没有团伙,单独做案,干净利落,便于封口。

若是你面对杀父仇人有这样的觉悟,那么纯函数即是你的囊中之物了。

为何要提纯函数?由于this.props就是汲取了纯函数的思想。

它最大的特色就是不可变。

this.state不同的是,this.props来真的。虽然this.state也反对开发者直接改变它的属性,但毕竟只是嘴上说说,仍是要靠开发者本身的约束。然而this.props会直接让你的程序崩溃。

加上React也没有this.setProps方法,因此不须要开发者自我约束,this.props就是不可变的。

沟通基本靠吼

父组件给子组件传值

这个无需赘言,最直观的传值方式。

import React from 'react';
import Child from './Child';

const App = () => {
    return (
        <Child star={1000} /> ); } export default App; 复制代码

子组件给父组件传值

其实就是利用回调函数的参数传递值。

父组件定义一个方法,将该方法经过props传给子组件,子组件须要给父组件传值时,便传参执行该方法。因为方法定义在父组件里,父组件能够接收到该值。

import React, { Component } from 'react';
import Child from './Child';

class App extends Component {
    state = { value: '' };
    
    render() {
        return (
            <Child handleSomething={this.handleSomething} /> ); } handleSomething = (e) => { this.setState({ value: e.target.value }); } } export default App; 复制代码
import React from 'react';

const Child = (props) => {
    return (
        <input type="text" onChange={props.handleSomething} /> ); } export default Child; 复制代码

兄弟组件之间传值

原理和回调函数同样,只不过这里父组件只是一个桥梁。

父组件接收到回调函数的值之后,经过this.setState保存该值,并触发另外一个子组件从新渲染,从新渲染后另外一个子组件即可以得到该值。

import React, { Component, Fragment } from 'react';
import ChildA from './ChildA';
import ChildB from './ChildB';

class App extends Component {
    state = { value: '' };
    
    render() {
        return (
            <Fragment>
                <ChildA handleSomething={this.handleSomething} />
                <ChildA value={this.state.value} />
            </Fragment>
        );
    }
    
    handleSomething = (e) => {
        this.setState({ value: e.target.value });
    }
}

export default App;
复制代码
import React from 'react';

const ChildA = (props) => {
    return (
        <input type="text" onChange={props.handleSomething} /> ); } export default ChildA; 复制代码
import React from 'react';

const ChildB = (props) => {
    return (
        <div>{props.value}</div>
    );
}

export default ChildB;
复制代码

createContext

👽这是React v16.3.0发布的API。

React为开发者提供了一扇传送门,它就是Context对象。

严格来讲,Context早就存在于React中了,不过一直以来都不是正式的API。

终于在v16.3.0转正了。

为何说Context是一扇传送门?由于它能够跨组件传递数据。不是父子之间的小打小闹哦,而是能够跨任意层级。可是有一个限制,数据只能向下传递,缘由就是后面要讲到的单向数据流。

开发者经过createContext建立一个上下文对象(React特别喜欢create),而后找一个顶级组件做为Provider。接下来就能够在任意下级组件消费它提供的数据了。

  • 只要Provider的数据改变,就会触发Consumer的更新。
  • 建立时能够提供一个默认值,另外挂载时能够经过value属性传递数据。可是默认值只有在不提供Provider的状况下才起做用。
  • 开发者能够建立多个Context。
  • Consumer的children必须是一个函数。

旧的Context存在一个问题,若是接收组件的shouldComponentUpdate生命周期钩子返回false,则它不会接收到Context中的数据,由于它是经过this.props一级一级往下传的。

而新的Context采起的是订阅发布模式,因此不存在这个问题。

实际上react-redux库的Provider组件内部就是使用了旧的Context API,不过redux作了一些优化。

import { createContext } from 'react';

const { Provider, Consumer } = createContext({ lang: 'en' });

export { Provider, Consumer };
复制代码
import React, { Component } from 'react';
import { Provider } from './context';
import Todos from './Todos';

const App = () => {
    return (
        <Provider value={{ lang: 'zh' }}> <Todos /> </Provider>
    );
}

export default App;
复制代码
import React, { Fragment } from 'react';
import TodoItem from './TodoItem';

const Todos = () => {
    return (
        <Fragment> <TodoItem /> <TodoItem /> <TodoItem /> </Fragment>
    );
}

export default Todos;
复制代码
import React from 'react';
import { Consumer } from './context';

const TodoItem = () => {
    return (
        <Consumer> {({ lang }) => <div>{lang === 'en' ? 'todo' : '要作'}</div>} </Consumer>
    );
}

export default TodoItem;
复制代码

单向数据流

水往低处流,这是天然规律。

React经过描述状态来控制UI的表达,这就涉及到UI的更新机制。

状态除了内部状态以外,确定有一些状态是要组件之间共享的,因此,一旦一个组件的状态更新了,可能会牵扯到不少组件的更新,框架的更新机制必将变的异常复杂。

可是回归到水的意象,若是状态的流向是单向的,并且是自上往下流动,这就变的很是符合直觉,并且更新机制能够作到极简:我更新,则个人全部下级也更新。

这就是this.props的思想源头。

它虽然叫props,但它也是状态,只不过是共享的状态。

它只能自顶向下流动。

内部不能改变this.props

某个props的源头更新了,则流经的全部组件都要更新,除非开发者手动禁止。

脉络清晰,this.props才是赋予了React血液的东西。

关于React摒弃了表单双向数据绑定的问题,它只是想把单向数据流作的更完全一点。其实表单的状态,归根结底是组件内部的状态,跟单向数据流无关。

什么是双向数据绑定?就是表单输入,与之绑定的变量自动获取到输入的值,变量的值改变,与之绑定的表单的值随即改变,两种流向都自动绑定了。

但其实双向数据绑定不就是value的单向绑定加onChange事件监听么!React也能够经过两步作到。

总结:双向数据绑定不影响单向数据流,React也能够实现双向的同步。

React专题一览

什么是UI

JSX

可变状态

不可变属性

生命周期

组件

事件

操做DOM

抽象UI

相关文章
相关标签/搜索