React 系统性学习(上)

作者: 果汁凉茶丶 | 来源:发表于2018-06-13 20:03 被阅读367次

    $ 前言

      最近在考虑框架转型,鉴于作为一名JSer,要时时刻刻保持对新技术和流行技术的敏感性,而 React、Vue、Angular 已基本占领现前端市场,React作为领头大哥,建议年轻的JSer们都要学习使用或至少了解这门技术。

    $ 版本声明

      本文使用版本 React v16.2.0

    $ 什么是 React ?

      React是一个声明式的,高效的,并且灵活的用于构建用户界面的 JavaScript 库

      一个最简单的React例子

    ReactDom.render(
        <h1>Hello World</h1>,
        document.getElementById('root')
    )
    

    ReactDom.render接受两个参数,第一个是要被插入的内容,第二个是插入到DOM或者说index.html的位置

    $ 一个与Html对比的简单组件

      如下是一个 React 组件

    class ShoppingList extends React.Componnet {
        render() {
            return (
                <div className="shopping-list">
                    <h1>Shoping List for {this.props.name}</h1>
                    <ul>
                        <li>Instagram</li>
                        <li>WhatApp</li>
                        <li>Oculus</li>
                    </ul>
                </div>
            )
        }
    }
    
    // Example usage:  <ShoppingList name="Mark" />
    

      在这里,ShoppingList是一个 React组件类,或 React组件类型。组件接受参数,称为属性 props, 并通过 render方法返回一个现实的视图层次结构。

    render 方法返回您要渲染的内容描述,然后React接受该描述并将其渲染到屏幕上,特别是,render 返回一个React 元素,这是一个渲染内容的轻量级的描述。大多数
    React 开发人员使用 JSX 语法,也是上述案例写到的语法。

    JSX 语法的转换规则为: <div /> 语法在构建是被转换为 React.createElement('div')。因此,上面的例子等价于:

    return React.createElement('div', {className: 'shopping-list'},
        React.createElement('h1', /* h1 children ... */),
        React.createElement('ul', /* ul children ... */)
    );
    

      既然 JSX 在 React 开发者中这么流行,那 JSX 又是什么呢?

    $ JSX 语法

    JSX 它是 Javascript 的一种拓展语法,能够让你的 Javascript 中和正常描述 HTML一样编写 HTML。

      你可以用 花括号 将任意 Javascript 表达式嵌入到 JSX 中。例如:表达式 1 + 2, 变量 user.firstName, 和函数 formatName(User) 等都可以嵌入使用

    function formatName(user) {
        return user.firstName + ' ' + user.lastName;
    }
    
    const user = {
        firstName: 'harper',
        lastName: 'Perez'
    }
    
    const element = {
        <h1> Hello, {formatName(user)}! </h1>
    }
    
    ReactDOM.render (
        element,
        document.getElementById('root')
    )
    

      请注意,为了方便阅读开发者们常将 JSX分割成多行包裹起来,因为这可以避免分号自动插入的陷阱,如

    { 1
    2 } 3
    // is transformed to
    { 1
    ;2 ;} 3;
    
    JSX 也是一个表达式

      编译之后, JSX 表达式也就成了一个常规的 javascript 对象

      也正因为如此,我们可以在 if 语句或这是 for 循环语句中使用 JSX,用它给变量赋值,当做参数接受,或者作为函数的返回值

    function getGreeting(user) {
        if (user) {
            return <h1>Hello. {formatName(User}</h1>;
        }
        return <h1>Hello, Stranger</h1>
    }
    
    用 JSX 指定属性值

      你可以用花括号嵌入一个 JavaScript 表达式作为属性值

    // 用引号形式
    const element = <div tableIndex="0"></div>;
    // 用表达式,并且表达式用花括号包裹
    const element = <img src={user.avatarUrl}></img>;
    
    用 JSX 指定子元素

      如果是空标签,可以直接用 /> 闭合

    const element = <img src={user.avatarUrl} />
    

      如果包含子标签:

    <div>
        <h1>Hello!</h1>
        <h2>Good to see you here.</h2>
    </div>
    

    比起 HTMLJSX 更接近于Javascript,所以React DOM规范使用驼峰(camelCase)属性命名约定,而不是HTML属性名称,当然,html的部分属性名称也作为保留字,不可使用,例如 class
    因此,class 在 JSX 中 变为 className, tableindex 变为 tableIndex

    用 JSX 防止注入攻击

      在JSX 中嵌入用户输入是安全的:

    const title = response.potentiallyMaliciousInput;
    // 这样是安全的
    const element  = <h1>{title}</h1>
    

      默认情况下, 在渲染之前, React DOM 会格式化(escapes) JSX中的所有值. 从而保证用户无法注入任何应用之外的代码. 在被渲染之前,所有的数据都被转义成为了字符串处理。 以避免 XSS(跨站脚本) 攻击。

    $ 元素渲染到DOM

      正常情况下,你的 index.html 文件下会有这么一个div

    <div id='root'></div>
    

      这个root DOM 节点挂在所有React DOM的位置。正常情况下,对于一个React单页面应用构建,只需要一个单独的根DOM节点即可。但如果要把React整合到现有的APP中,则可能会使用到多个DOM节点。

      React利用render方法将React元素渲染到DOM上,一旦元素被渲染到页面了之后,就不能在修改器子元素或任何元素的属性,就像电影里的一帧,在某以特定的时间点的UI效果,那元素的更新呢?没错,就是重新 render

    function tick() {
        cosnt element = {
            <div>
                <h1>Hello, world</h1>
                <h2>It is {new Date().toLocaleTimeString()}.</h2>
            </div>
        };
        ReactDom.render (
            element,
            document.getElementById('root')
        )
    }
    
    setInterval(tick, 1000);
    

    实际上,大多数 React 应用只会调用一次ReactDom.render(),而实现组件更新的办法就是将代码封装在有状态的组件中。

    React 只更新必须更新的部分

      这正是 React 的强大之处。React DOM 会将元素及其子元素与之前版本逐一对比,并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。

      开发过程中,更应该每个时间点UI的表现, 而不是关注随着时间不断更新UI的状态, 可以减少很多奇怪的 bug

    $ 组件和属性

      组件 components 和属性 props,其中,属性是单词 property 的代码简写。

    定义组件的两种办法

      定义组件有两种方式

    1. 函数式组件定义
    2. 类组件定义
        最简单的定义组件的方法就是写一个 Javascript 函数
    function Welcome(props)  {
        return <h1>Hello, props.name</h1>
    }
    

      这就是一个有效的组件,它接首了一个 props 参数,并返回了一个React元素,这是一个函数式组件,表面上看,他就是一个 Javascript函数。

      类组件的定义则依赖ES6 的 class 来定义,下面这种定义方法和上方是等效的;

    class Welcome extends React.Component {
        render() {
            return <h1>Hello, {this.props.name}</h1>;
        }
    }
    
    渲染一个组件
    // DOM标签作为组件
    const element = <div />;
    // React 元素作为组件
    const element = <Welcome name="Sara" />;
    

      当React 遇到一个代表用户定义组件的元素时,它将 JSX 属性以一个单独对象即
    props对象 的形式传递给相应的组件,例如

    function Welcome(props) {
        return <h1>Hello, {props.mname] </h1>;
    }
    const element = <Wlecome name="Sara" />;
    ReactDOM.render(
        element,
        document.getElementById('root')
    )
    

    理解

    1. 调用 ReactDOM.render() 方法并向其传入了<Welcome name="Sara" />元素
    2. Raect 调用 Welcome 组件,并向其传入了 {name: ‘Sara’} 作为 props对象
    3. Welcome 组件返回 <h1>Hello, Sara</h1>
    4. React DOM 迅速更新 DOM,使其显示为 <h1>Hello, Sara</h1>

    组件名称总是以大写字母开始, 如本例子中 <Welcome />, 而不是 <welcome />

    构成组件

      既然组件是单独的一个React元素,那他能单独工作,因此我们能在一个React 元素中多次引用到相同的组件, 举个例子:

    function Welcome(props) {
        return <h1>Hello, {props.name}</h1>
    }
    function App() {
        return (
            <Welcome name="Sara" />
            <Welcome name="Lucy" />
            <Welcome name="Edite" />
        )
    }
    
    ReactDOM.render(
        <App />,
        document.getElementBuId('root')
    )
    

      通常情况下, React apps 都有一个单独的顶层的 App 组件。如果是在已有的应用中整合React,也需要由下至上的从小的组件开始逐步整合到视图顶层的组件中。

    组件必须返回一个单独的根元素,这就是为什么我们要添加一个 <div>来包裹所有的<Welcome /> 元素的原因

    提取组件

      对于一个React 元素,如果其中含有可复用或可能会重复使用的内容,不要害怕把它单拿出来多个更小的组件。

      提取组件可能看起来是一个繁琐的工作,但是在大型的 App 中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (ButtonPanelAvatar),或者本身足够复杂(AppFeedStoryComment),最好的做法是使其成为可复用组件。

    Props 是只读的

      无论你用函数或类的方法来声明组件,

      虽然 React 很灵活,但是它有一条严格的规则:**所有 React 组件都必须是纯函数,并禁止修改其自身 props **。所谓的纯函数就是:传入函数参数不会在函数执行过程中发生改变,比如自增操作 a++

      如果props是只读的,那传递给子元素(子组件)的参数岂不是不能修改了?那子元素如何与父元素做交互呢?React还给我们提供了状态属性 state供我们在子组件内部修改值

    状态和生命周期

      状态state, 生命周期 liftcircle.
      之前说过,一旦元素被渲染了之后就不可改变了,但我们可以通过重新渲染的方法使页面得以刷新,同样我们提到过最常用的方法是编写一个可复用的具有状态的组件,这里的状态,就是我们将要说的 state

      我们对上述提过的计时器tick 中的计时功能封装成一个函数式组件如下:

    function Clock(props) {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {props.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
    

      然后把他当做一个元素放入 tick 中进行渲染

    function tick() {
        ReactDOM.render(
            <Clock date={new Date()} />,
            document.getElementById('root')
        )
    }
    
    setInterval(tick, 1000);
    

      在这个例子中,我们将计时功能代码封装成了一个独立的可复用的组件,并通过属性date的方式将参数传入,但还不能到达我们想要的结果,那就是不能再组件内部修改参数值,组件中显示的数据依旧受控于父组件中date属性传递过来的值,那如果我们把这个date属性也添加到Clock内部呢?来看下

    ReactDOM.render(
        <Clock />,
        document.getElementById('root')
    )
    

      这时父组件中只保留了对计时组件Clock的一个单纯的引用。剩下的事情全部依托以组件Clock自己去实现。要怎么实现这个需求?这里React提出了另一个数据对象,即state,它用来保存组件内部的数据,与props类似,不同的是state是组件私有的,并且由组件本身完全控制。它能实现数据在组件内部的修改和更新。怎么使用这个state?继续往下讲之前我们先拓展一个知识

      我们知道组件有两种定义方式,即函数式组件和类组件,虽然函数式组件更加简洁更加接近原生 javascript,但类组件却拥有一些额外的属性,这个类组件专有特性,就是状态生命周期钩子,到这里也能清楚知道状态的关键作用,然而函数式组件没有这两个特性,因此,在需要使用到状态state情况下,我们需要将函数式组件转换成类组件

    函数式组件转化成类组件

      尝试把一个函数式组件转化成类组件,官网给出了以下步骤,以Clock组件为例

    1. 创建一个继承自 React.Component 类的 ES6 class 同名类
    2. 添加一个名为 render() 的空方法
    3. 把原函数中的所有内容移至 render()
    4. render() 方法中使用 this.props 替代 props
    5. 删除保留的空函数声明
    class Clock extents React.Component {
        render() {
            return (
                <div>
                    <h1>Hello, world</h1>
                    <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
                </div>
            )
        }
    }
    

      到此,Clock 组件已经成功被我们修改成了一个类组件,我们便可以在其中添加本地状态state和生命周期钩子

    class Clock extends React.Component {
        // 用类构造函数constructor初始化 this.state
        constructor(props) {
            // 使用super()将props传递给基础构造函数
            super(props);
            this.state = {date: new Date()};
        }
    
        render() {
            return (
                <div>
                    <h1>Hello, world</h1>
                    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
                </div>
            )
        }
    }
    

      这样,我们的类组件Clock 就拥有了自己的属性 this.state.date,也就不需要引用组件向其传递值了,因此,我么可以把组件引用中的date属性删掉,最终,我们将其渲染到DOM上,只使用组件引用,其他都交给组件Clock自己去实现

    ReactDOM.render(
        <Clock />,
        document.getElementById('root')
    )
    

      到这里就结束了?细心的你会发现,组件Clock只是实现了当前时间的显示,而我们要改装的功能是一个计时器,计时功能去哪里了?没实现啊?我们需要在组件Clock中找到一个合适的时机去实现这个功能,为此,React团队引入了 声明周期方法,也叫生命周期钩子

    在类组件中添加生命周期方法

      在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。就像浏览器的垃圾回收机制,近期内不需要再用的资源,应该及时清除。

      当 Clock 第一次渲染到DOM时,我们要设置一个定时器 。 这在 React 中称为 “挂载(mounting)” 。它有一个生命钩子componentDidMount()

    Clock 产生的 DOM 被销毁时,我们也想清除该计时器。 这在 React 中称为 “卸载(unmounting)” 。它的生命钩子是componentWillUnmount()

      我们的计时器是在页面加载之后,页面生成初始化状态,然后由计时器去触发状态的刷新,因此,在挂载完成是去设置计时器是个非常不错的选择

    componentDidMount() {
        this.timerID = setInterval(
            () => this.tick(), 1000
        )
    }
    

      这样我们就实现了组件计时功能,或许你注意到了,在该例中,我们把timerID存放在this中而不是this.state中。

      其实,this.propsthis.state也是数据对象与普通对象一样用来存放数据,只是他们被React团队赋予了新的职能, this.props由React本身设定,用来存放在组件引用时的属性键值对对象集,不允许Coder们自己去修改;而this.state也具有特殊的含义,即存放组件本身的、用于视觉输出的数据,但也不是说在编写React程序的时候就必须用用这两个,我们依然可以自己定义普通的数据结构。

      既然state是用于存放组件视觉输出的数据,那在render()方法中没有被引用的,就不应该出现在state中了。

      养成良好的编码习惯,编写好计时器时,及时的编写卸载事件。卸载时我们清除的数据也是从this中拿的。

    componentWillUnmount() {
        clearInterval(this.timerID);
    }
    

      挂载时我们声明了一个tick()方法,接下来我们就要实现这个方法,是用来触发UI更新。嗯哼?UI更新?我们的页面状态state不是已经更新了吗?为啥还要UI更新?

      这里有一个非常重要的方法:setState()。我们先把代码补充完整再说明

    componentDidMount() {
        // ...
    }
    
    tick() {
        this.setState({
            date: new Date()
        })
    }
    
    componentWillUnmount() {
        // ...
    }
    

    setState()是React触发页面更新的第二个办法,第一个办法开篇即说过,即render()方法。setState作用就是通知React检查带状态的组件中是否含有脏值。此时react会生成一个虚拟DOM与之前的版本进行对比,只有有必要更新时才会更新。关于 state 与 setState过程 在我的另一篇文章中有详细说明,有兴趣的可以翻过去看看。

      为什么不把tick()方法写到componentDidMount()中?因为tick()只是一个普通方法,他不需要在生命周期中触发,也不用自动触发。只要谁调用了触发即可。因此不需要也不能放在生命周期钩子函数中。

      现在这个时钟每秒都会走了。整理一下,我们整个计时器代码如下:

    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {date: new Date()};
      }
    
      componentDidMount() {
        this.timerID = setInterval(
          () => this.tick(),
          1000
        );
      }
    
      componentWillUnmount() {
        clearInterval(this.timerID);
      }
    
      tick() {
        this.setState({
          date: new Date()
        });
      }
    
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );
    

    整个流程的执行过程是这样的:

    1. <Clock /> 被传入ReactDOM.render() 时, React 会调用 Clock组件的构造函数。 因为 Clock 要显示的是当前时间,所以它将使用包含当前时间的对象来初始化 this.state。我们稍后会更新此状态。

    2. 然后 React 调用了 Clock 组件的 render() 方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配 Clock 的渲染输出。

    3. Clock 输出被插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。在该方法中,Clock 组件请求浏览器设置一个定时器来一次调用 tick()

    4. 浏览器会每隔一秒调用一次 tick()方法。在该方法中, Clock 组件通过 setState() 方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。 这次,render() 方法中的 this.state.date 的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。

    5. 如果通过其他操作将 Clock 组件从 DOM 中移除了, React 会调用 componentWillUnmount() 生命周期钩子, 所以计时器也会被停止。

    正确的使用State(状态)

      对于setState() 有三件事情是我们应该要知道的

    (1)不要直接修改state
      真正触发React对比不同版本的虚拟DOM是setState() 方法,直接修改state页面不会刷新,这一点与原生javascript区别较大,需要理解。

    // 这么做不会触发React更新页面
    this.state.comment = 'hello';
    // 使用 setState() 代替
    this.setState({ comment: 'hello' });
    

      【注意】在组件中,唯一可以初始化分配this.state的地方就是构造函数constructor(){}

    (2)state(状态)更新可能是异步的
      React为了优化性能,有可能会将多个setState() 调用合并为一次更新。这就导致 this.propsthis.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)

    // counter 计数更新会失败
    this.setState({
        counter: this.state.counter this.props.increment
    })
    

      如果我们有这种需求,可以使用以下setState()办法:

    // ES6 箭头函数法
    this.setState((prevState, props) => ({
        counter: prevState.counter + props.increment
    }));
    // 常规函数法
    this.setState(function(prevState, props) {
        return {
            counter: prevState.counter + props.increment
        };
    })
    

    (3)state(状态)更新会被合并
    当你调用setState(), React将合并你提供的对象到当前状态中。例如,你的状态可能包含几个独立的变量,然后你用几个独立的setState方法去调用更新,如下

    constructor(props) {
        super(props);
        this.state = {
            posts: [],
            comments: []
        };
    }
    
    componentDidMount() {
        fetchPosts().then(response => {
            this.setState({
                posts: response.posts
            });
        });
    
        fetchComments().then(response => {
            this.setState({
                comments: response.comments
            });
        });
      }
    

      合并是浅合并,所以,this.setState({comments})在合并过程中不会改变this.state.posts的值,但是会完全替换this.state.comments 的值

    数据向下流动

      无论是作为父组件还是子组件,它都无法或者一个组件是否有状体,同时也不需要关心另一个组件是定义为函数组件还是类组件。这就是为什么state经常被称为 本地状态封装状态 的原因, 他不能被拥有并设置它的组件以外的任何组件访问。那如果需要访问怎么处理?
    (1)作为其子组件的props(属性)

    // 在组件中使用
    <h2>It is {this.state.date.toLocaleTimeString()}</h2>
    // 传递给子组件作为props
    <FormattedDate date={this.state.date} />
    

      虽然FormattedDate组件通过props接收了date的值,但它仍然不能获知该值是来自于Clockstate, 还是 Clockprops, 或者一个手动创建的变量.

      这种数据关系,一般称为"从上到下"或"单向"的数据流。任何state(状态)始终由某个特定组件所有,并且从该state导出的任何数据 或 UI 只能影响树"下方"的组件

      如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。

    各组件完全独立

      借用上文的Clock组件,我们创建一个App组件,并在其中渲染三个Clock:

    function App() {
        return (
            // 之前说过组件只能返回一个根节点,所以用<div>包起来
            <div>
                <Clock />
                <Clock />
                <Clock />
            </div>
        );
    }
    ReactDOM.render(
        <App />,
        document.getElementById('root')
    );
    

      每个Clock都设立它自己的计时器并独立更新,如果App中有一个数据变量,也能被三个Clock相互独立修改。

      至于何时使用有状态组件,何时使用无状态组件,被认为是组件的一个实现细节,取决于你当时的需求,你可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件

    $ 事件处理

      通过 React 元素处理事件跟在 DOM 元素上处理事件非常相似。但是有一些语法上的区别:

    1. React 事件使用驼峰命名,而不是全部小写
    2. 通过 JSX , 传递一个函数作为事件处理程序,而不是一个字符串
    // html usage
    <button onclick="todo()">click me</button>
    // React usage
    <button onClick={todo}>click me></button>
    
    1. 在React中不能通过返回false来阻止默认行为。必须明确的调用preventDefault
    // html usage
    <a href="#" onclick="console.log('clicked'); return false">
        Click me
    </a>
    
    // React usage
    class ActionLink extends React.Component {
        function handleClick(e) {
            e.preventDefault();
            console.log('clicked.');
        }
        return (
            <a href="#" onClick={handleClick}>
                Click me
            </a>
        )
    }
    

      在这里,React团队帮Coder们实现了e事件的跨浏览器兼容问题。当使用React时,我们也不需要调用addEventListener在DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了。

      当使用ES6类定义一个组件时,通常的一个事件处理程序就是类上的一个方法,看个例子,Toggle 组件渲染一个按钮,让用户在 “ON” 和 “OFF” 状态之间切换:

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
    
        // 这个绑定是必要的,使`this`在回调中起作用
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }
    
    ReactDOM.render(
      <Toggle />,
      document.getElementById('root')
    );
    
    绑定类方法

      在JSX回调中你必须注意 this 的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick 并将其传递给onClick,那么在直接调用该函数时,this 会是 undefined

     这不是 React 特有的行为;这是 JavaScript 中的函数如何工作的一部分,可以使用属性初始值设置来正确地 绑定(bind) 回调,但这是实验性做法,不建议使用,以后有可能会废弃,如果你没有使用属性初始化语法
    (1)可以在回调中使用一个 arrow functions

    class LoginButton extends React.Component {
        handleClick() {
            console.log('this is: ', this)
        }
    
        render() {
            // 这个语法确保 `this` 被绑定在 handleClick 中
            return (
                <button onClick={(e) => this.handleClick(e)}>
                    Click me
                </button>
            );
        }
    }
    

    (2)使用Function.prototype.bind 方法,相对简洁方便

    <button onClick={this.handleClick.bind(this)}>
        Click me
    </button>
    
    传递参数给事件处理程序

      在循环内部,通常需要将一个额外的参数传递给事件处理程序,常用的有一下两种方案;

    <button onClick={(e)  => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this.id)}>Delete Row</button>
    

      上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

    $ 条件渲染

    在 React 中,你可以创建不同的组件封装你所需要的行为。然后,只渲染它们之中的一些,取决于你的应用的状态。

    整个组件的条件渲染

      React 中的条件渲染就可在JS中的条件语句一样,使用JS操作符如if或者条件控制符来创建渲染当前的元素,并且让React更新匹配的UI。比如我们有一个需求,需要判断用户是否登录,来显示不同组件

    function UserGreeting(props) {
        return <h1>Welcome back!</h1>
    }
    
    function GustGrreeting(props) {
        return <h1>Please sign up.</h1>
    }
    
    function Greeting(props) {
        const isLoggedIn = props.isLoggedIn;
        if (isLoggedIn) {
            return <UserGreeting />
        } 
        return <GuestGreeting />
    }
    
    ReactDOM.render(
        <Greeting isLoggedIn={false} />,
        document.getElementById('root')
    );
    
    使用元素变量条件渲染部分内容

      你可以用变量来存储元素。这可以帮助您有条件地渲染组件的一部分,而输出的其余部分不会更改。下方两个组件用于显示登出和登入按钮

    function LoginButton() {
        return(
            <button onClick={props.onClick}>Login</button>
        )
    }
    
    function LogoutButton(props) {
        return (
            <button onClick={props.onclick}>Logout</button>
        )
    }
    

      登入登出按钮已做好,接下来需要实现有切换功能的一个有状态的组件,为了更系统化学习,我们把前面的Greeting组件一起加进来

    class LoginControl extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                isLoginedIn: false
            }
        }
    
        handleLoginClick() {
            this.setState({   isLoggedIn: true });
        }
    
        handleLogoutClick() {
            this.setState({ isLoggedIn: false });
        }
    
        render() {
            const isLoggedIn = this.state.isLoggedIn;
    
            let button = null;
            if (isLoggedIn) {
                button = <LogoutButton onClick={this.handleLogoutClick.bind(this)} />
            } else {
                button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
            }
    
            return (
                <div>
                    <Greeting isLoggedIn={isLoggedIn} />{button}</div>
                </div>
            )
        }
    }
    
    reactDOM.render(
        <LoginControl />,
        document.getElementById('root')
    )
    

      使用if是很常见的一种做法,当然也有一些更简短的语。JSX中有几种内联条件的方法,

    (1)使用逻辑与&&操作符的内联if用法
      我们可以在 JSX 中嵌入任何表达式,方法是将其包裹在花括号中,同样适用于JS的逻辑与&&运算符

    function Mailbox(props) {
        const unreadMessages = props.unreadMessages;
        return (
            <div>
                <h1>Hello!</h1>
                { unreadMeaasges.length > 0 &&
                    <h2> You have {unreadMessages.length} unread messages.
                }
            </div>
        )
    }
    
    cosnt message = ['React', 'Re: React', 'Re:Re: React'];
    ReactDOM.render(
        <Mailbox unreadMessages={messages} />,
        document.getElementById('root')
    );
    

      该案例是可以正常运行的,因为在 JavaScript 中, true && expression 总是会评估为 expression ,而 false && expression 总是执行为 false 。并且我们可以在表达式中嵌入表达式

    (2)使用条件操作符的内联If-Else
      条件操作符 即三目表达式:condition ? trueExpression : falseExpression

    // 条件渲染字符串
    <div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
    // 条件渲染组件
    <div>
        {isLoggedIn ? (
            <LogoutButton onClick={this.handleLogoutClick} />
        ) : (
            <LoginButton onClick={this.handleLoginClick} />
        )}
    </div>
    

      总之,遵循一个原则,哪种方式易于阅读,就选择哪种写法。并且,但条件变得越来越复杂时,可能是提取组件的好时机。

    阻止组件渲染

      在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null 而不是其渲染输出。注意这里是不渲染,不是不显示。

      在下面的例子中,根据名为warnprops 值,呈现 <WarningBanner /> 。如果 props 值为 false ,则该组件不渲染:

    function WarningBanner(props) {
        if (props.warn) { 
            return null;
        }
        
        return (
            <div className="warning">Warning</div>
        )
    }
    
    class Page extends React.Component {
        constructor(props) {
            super(props);
            this.state = { showWarning: true }
        }
    
        handleToggleClick() {
            this.setState(prevState => ({
                showWarning: !prevState.showWarning
            }));
        }
    
        render() {
            return (
                <div>
                    <Warningbanner warn={this.state.showWarning} />
                    <button onClick={this.handleToggleClick.bind(this)}>
                        { this.state.showWarning ?   'Hide' : 'Show'}
                     </button>
                </div>
            )
        }
    }
    
    ReactDOM.render(
        <Page />,
        document.getElementById('root')
    )
    

      从组件的 render 方法返回 null 不会影响组件生命周期方法的触发。 例如, componentWillUpdatecomponentDidUpdate 仍将被调用。因此需要组件刚载入时就要判断执行返回null

    $ 后语

      本文为React系统性需学习上半文,下半文主要包括:

    1. 列表(List) 和 键(keys)
    2. 表单(Forms)
    3. 状态提升(Lifting State Up)
    4. 组合 VS 继承 (Composition vs inheritance)
        如果有错误,欢迎大家指正,也欢迎大家到评论区交流共同进步

    相关文章

      网友评论

        本文标题:React 系统性学习(上)

        本文链接:https://www.haomeiwen.com/subject/zpujeftx.html