微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

所有这些基础的 React.js 概念都在这里了

2017-09-05 19:50 前端大全

(点击上方公众号,可快速关注)

英文:Samer Buna    译文:众成翻译 

www.zcfy.cc/article/all-the-fundamental-react-js-concepts-jammed-into-this-single-medium-article-4030.html

如有好文章投稿,请点击 → 这里了解详情



去年,我写了一本关于学习React.js的简短书,原来是大约100页。今年,我将挑战自己,把它概括为一篇关于中度的文章。


这篇文章不会涵盖什么是React或者为什么你应该学习它。相反,这是对已经熟悉JavaScript并熟悉DOM API基础知识的人们对React.js的基础知识的实践介绍。


以下所有代码示例均标示为参考。它们纯粹是为了提供概念的例子。他们大多数可以写得更好一些。


基础 #1:React都是组件


React是围绕可重用组件的概念设计的。您定义小组件,并将它们放在一起以形成更大的组件。


所有小或大的组件都可重复使用,甚至跨不同的项目。


一个React组件(以其最简单的形式)是一个简单的JavaScript函数:。


示例1https://jscomplete.com/repl?j=Sy3QAdKHW


function Button (props) {

  // 返回一个DOM元素。例如:

  return <button type="submit">{props.label}</button>;

}


//  将Button组件渲染到浏览器

ReactDOM.render(<Button label="Save" />, mountNode)


下面说明用于按钮标签的花括号。现在不要担心他们。ReactDOM稍后也会进行说明,但是如果要测试这个例子和所有即将到来的代码示例,上面的render 函数就是你所需要的。


第二个参数ReactDOM.render 是React将要接管和控制的目标DOM元素。在jsComplete REPL中,您可以使用特殊变量mountNode。


请注意以下关于示例1:


  • 组件名称以大写字母开头。这是必需的,因为我们将处理HTML元素和React元素的混合。小写名称保留给HTML元素。事实上,请继续尝试将React组件命名为“button”。ReactDOM将忽略该函数并渲染一个常规的空HTML按钮。

  • 每个组件都接收一个属性列表,就像HTML元素一样。在React中,这个列表被称为props。使用函数组件,您可以命名任何东西。

  • 我们奇怪地在上面的Button函数组件的返回输出中写出了什么样的HTML 。这既不是JavaScript也不是HTML,甚至不是React.js。但是,它非常受欢迎,成为React应用的默认设置。它被称为JSX ,它是一个JavaScript扩展。JSX也是折衷!继续尝试并返回上面的函数中的任何其他HTML元素,并查看它们是如何支持的(例如,返回一个文本输入元素)。


基本原理 #2:JSX有什么好处?


上面的示例1可以用纯粹的React.js来编写,没有JSX,如下所示:


示例2 – 不用JSX的React 组件 https://jscomplete.com/repl?j=HyiEwoYB-


function Button (props) {

  return React.createElement(

    "button",

    { type: "submit" },

    props.label

  );

}


// 要使用Button,您可以执行类似这样

ReactDOM.render(

  React.createElement(Button, { label: "Save" }),

  mountNode

);


该 createElement 函数是React顶级API中的主要函数。您需要学习的这个级别中共有7件事情中的1项。这是多么小的React的API。


很像DOM本身具有document.createElement 创建由标签名称指定的元素的函数,React的createElement函数是一个更高级别的函数,可以做document.createElement 能做的任何事。但它也可以用于创建一个元素来表示一个React组件。我们使用上面的例2中的组件Button 时,我们用了后者。


不同于document.createElement,React 的createElement 接受第二个参数后的动态数量来表示创建元素的子代。所以createElement 实际上创建一个树。


这是一个例子:


示例3 – React的createElement API https://jscomplete.com/repl?j=r1GNoiFBb


const InputForm = React.createElement(

  "form",

  { target: "_blank", action: "[https://google.com/search](https://google.com/search)" },

  React.createElement("div", null, "Enter input and click Search"),

  React.createElement("input", { className: "big-input" }),

  React.createElement(Button, { label: "Search" })

);


// InputForm使用Button组件,所以我们也需要:

function Button (props) {

  return React.createElement(

    "button",

    { type: "submit" },

    props.label

  );

}


// 然后我们可以直接使用InputForm

ReactDOM.render(InputForm, mountNode);


请注意以上几个例子:


  • InputForm 不是React组件; 它只是一个React 元素。这就是为什么我们直接在ReactDOM.render 中调用它,而不是与。

  • 该React.createElement 函数在前两个之后接受多个参数。从第3个起始的参数列表包括创建元素的子项列表。

  • 我们能够嵌套React.createElement ,因为它都是JavaScript。

  • React.createElement 当元素不需要属性或特性时,第二个参数可以为null或空对象。

  • 我们可以将HTML元素与React组件混合使用。您可以将HTML元素视为内置的React组件。

  • React的API尝试尽可能接近DOM API,这就是为什么我们使用className 而不是class 输入元素。秘密地,我们都希望React的API将成为DOM API本身的一部分。因为,你知道,这好多了。


上面的代码是您在包含React库时了解的内容。浏览器不处理任何JSX业务。然而,我们人类喜欢看HTML并且使用HTML而不是这些createElement 调用(想象一下建立一个网站仅使用document.createElement,你可以!)。这就是为什么存在JSX妥协的原因。React.createElement我们可以用非常类似于HTML的语法来编写上面的表单:


示例4 – JSX(与示例3相比) https://jscomplete.com/repl?j=SJWy3otHW


const InputForm =

  <form target="_blank" action="[https://google.com/search](https://google.com/search)">

    <div>Enter input and click Search</div>

    <input className="big-input" name="q" />

    <Button label="Search" />

  </form>;


// InputForm "仍然" 使用按钮组件, 所以我们也需要它。

// 无论是 JSX 或正常的形式

function Button (props) {

  // Returns a DOM element here. For example:

  return <button type="submit">{props.label}</button>;

}


// 然后, 我们可以在.render里直接使用 InputForm

ReactDOM.render(InputForm, mountNode);


请注意以上几点:


  • 它不是HTML。例如,我们仍在使用 className 而不是class。

  • 我们仍然在考虑将以上HTML作为JavaScript。看看我在末尾添加了分号。


我们上面写的(例4)是JSX。然而,我们对浏览器的编译是它的编译版本(示例3)。为了实现这一点,我们需要使用预处理器将JSX版本转换为React.createElement 版本。


那就是JSX。这是一个妥协,允许我们以类似于HTML的语法编写我们的React组件,这是一个很好的交易。


上面标题中的“Flux”一词被选为韵词,但它也是Facebook 流行的非常受欢迎的 应用架构 的名称。最着名的实现是Redux。Flux完美适应React反应模式。

JSX,顺便说一下,可以自己单独使用。这不是一个React唯一的事情。


基础 #3:您可以在JSX中的任何地方使用JavaScript表达式


在JSX部分中,您可以使用一对花括号内的任何JavaScript表达式。


*示例5 – 在JSX中使用JavaScript表达式 https://jscomplete.com/repl?j=SkNN3oYSW


const RandomValue = () =>

  <div>

    { Math.floor(Math.random() * 100) }

  </div>;


// 使用它:

ReactDOM.render(<RandomValue />, mountNode);


任何JavaScript表达式都可以放在那些花括号内。这相当于JavaScript 模板文字中的${} 插值语法。


这是JSX中唯一的约束:只有表达式。所以,例如,你不能使用一个常规if语句,但三元表达式是可以的。


JavaScript变量也是表达式,所以当组件接收到一个属性列表(RandomValue 组件没有,props 是可选的)时,可以在花括号内使用这些属性。我们在Button上面的组件中做了这个(例1)。


JavaScript对象也是表达式。有时候,我们在花括号内使用一个JavaScript对象,这使得它看起来像是双花括号,但它只是一个大括号内的对象。一个用例是将CSS样式对象传递给React中的特殊样式属性:


示例6 – 传递给特殊的React样式的对象prop https://jscomplete.com/repl?j=S1Kw2sFHb


const ErrorDisplay = ({message}) =>

  <div style={ { color: 'red', backgroundColor: 'yellow' } }>

    {message}

  </div>;


// 使用它:

ReactDOM.render(

  <ErrorDisplay

    message="These aren't the droids you're looking for"

  />,

  mountNode

);


请注意我是如何解体的只有消息出来的属性参数。这是JavaScript。还要注意上面的style属性是一个特殊的属性(再次,它不是HTML,它更接近于DOM API)。我们使用一个对象作为style属性的值。该对象定义了样式,就像我们使用JavaScript一样(因为我们就是)。 甚至可以在JSX中使用React元素,因为这也是一个表达式。记住,一个React元素是一个函数调用:


示例7 – 在React元素里面使用 {} https://jscomplete.com/repl?j=SkTLpjYr-


const MaybeError = ({errorMessage}) =>

  <div>

    {errorMessage && <ErrorDisplay message={errorMessage} />}

  </div>;

 

// MaybeError组件使用ErrorDisplay组件:

const ErrorDisplay = ({message}) =>

  <div style={ { color: 'red', backgroundColor: 'yellow' } }>

    {message}

  </div>;


// 现在我们可以使用 MaybeError 组件:

ReactDOM.render(

  <MaybeError

    errorMessage={Math.random() > 0.5 ? 'Not good' : ''}

  />,

  mountNode

);


上面的MaybeError 组件将仅显示ErrorDisplay 组件,如果有一个errorMessage 字符串传递给它,并且是空的div。React认为{true}, {false}, {undefined} 和 {null} 是有效的元素孩子,不渲染任何内容。


您也可以在JSX内使用所有的JavaScript函数方法的集合(map, reduce, filter, concat, 等)。再次,因为它们返回表达式:


示例8 – 在{}中使用数组 map https://jscomplete.com/repl?j=SJ29aiYH-


const Doubler = ({value=[1, 2, 3]}) =>

  <div>

    {value.map(e => e * 2)}

  </div>;


// 使用它

ReactDOM.render(<Doubler />, mountNode);


请注意,我如何将value prop设置为上面的默认值,因为它只是Javascript。还要注意,我在div输出了一个数组表达式。在React中这是可以的。它将在文本节点中放置2倍的值。


基础 #4:您可以使用JavaScript类编写React组件


简单的函数组件非常适合简单的需求,但有时我们需要更多的函数。React支持通过JavaScript类语法 创建组件。这是Button使用类语法编写的组件(在示例1中):


示例9 – 使用JavaScript类创建组件 https://jscomplete.com/repl?j=ryjk0iKHb


class Button extends React.Component {

  render() {

    return <button>{this.props.label}</button>;

  }

}


// 使用它(相同的语法)

ReactDOM.render(<Button label="Save" />, mountNode);


类语法很简单。定义一个React.Component的扩展类(需要学习的另一个顶级的React API)。该类定义单个实例函数render(),并且该render函数返回虚拟DOM对象。每次我们使用Button上面的基于类的组件(例如,通过这样做),React将从这个基于类的组件中实例化一个对象,并在DOM树中使用该对象。


这就是为什么我们在JSX中使用this.props.label 渲染输出的原因。因为每个组件都获得一个特殊的实例属性props,所以它被实例化时保存传递给该组件的所有值。


由于我们有一个与组件单次使用相关联的实例,我们可以根据需要自定义该实例。例如,我们可以通过使用常规JavaScriptconstructor 函数构建它来定制它:


示例10 – 自定义组件实例 https://jscomplete.com/repl?j=rko7RsKS-


class Button extends React.Component {

  constructor(props) {

    super(props);

    this.id = Date.now();

  }

  render() {

    return <button id={this.id}>{this.props.label}</button>;

  }

}


// 使用它

ReactDOM.render(, mountNode);


我们还可以定义类原型函数,并将它们随意使用,包括返回的JSX输出内:


示例11 — 使用类属性 https://jscomplete.com/repl?j=H1YDCoFSb


class Button extends React.Component {

  clickCounter = 0;


handleClick = () => {

    console.log(Clicked: ${++this.clickCounter});

  };

 

  render() {

    return (

      <button id={this.id} onClick={this.handleClick}>

        {this.props.label}

      </button>

    );

  }

}


// Use it

ReactDOM.render(<Button label="Save" />, mountNode);


注意上面关于示例11的几件事情:


  • 该handleClick 函数使用JavaScript中新提出的类字段语法 编写。这仍然在第二阶段,但由于很多原因,它是访问组件装载实例(感谢箭头函数)的最佳选择。但是,您需要使用像Babel这样的编译器来配置它来了解第2阶段(或类字段语法)来获取上面的代码。jsComplete REPL已预先配置。

  • 我们还使用相同的类字段语法定义了clickCounter 实例变量。这允许我们完全跳过使用类构造函数调用。

  • 当我们将该handleClick 函数指定为特殊onClick React属性的值时,我们没有调用它。我们通过在引用的handleClick函数。调用该级别的函数是使用React最常见的错误之一。


// 错误:

onClick={**this.handleClick()**}


// 正确:

onClick={**this.handleClick**}


基础 #5:React事件:两个重大差异


在React元素中处理事件时,与DOM API的方式有两个非常重要的区别:


  • 所有React元素属性(包括事件)使用camelCase命名,而不是小写。是onClick而不是onclick.。

  • 我们传递一个实际的JavaScript函数引用作为事件处理程序,而不是一个字符串。是onClick={**handleClick**}不是onClick="**handleClick"**。


使用自己的对象对DOM事件对象进行反射来优化事件处理的性能。但是在事件处理程序中,我们仍然可以访问DOM事件对象上可用的所有方法。React将包装的事件对象传递给每个句柄调用。例如,为了防止表单从默认提交操作中,您可以执行以下操作:


示例12 – 使用包装事件 https://jscomplete.com/repl?j=HkIhRoKBb


class Form extends React.Component {

  handleSubmit = (event) => {

    event.preventDefault();

    console.log('Form submitted');

  };

 

  render() {

    return (

      <form onSubmit={this.handleSubmit}>

        <button type="submit">Submit</button>

      </form>

    );

  }

}


// 使用它

ReactDOM.render(<Form />, mountNode);


基础 #6:每个React组件都有一个故事


以下仅适用于类组件(那些扩展自React.Component)。函数组件有一个略有不同的故事。


  1. 首先,我们为React定义一个模板,以从组件创建元素。

  2. 然后,我们指示React在某处使用它。例如,在render另一个组件的调用中,或ReactDOM.render。

  3. 然后,React实例化一个元素,并给出一组我们可以访问的 this.props属性。那些属性正是我们在上面的步骤2中传递的。

  4. 由于它都是JavaScript,所以constructor 将调用该方法(如果已定义)。这是我们所说的第一个:组件生命周期方法。

  5. React然后计算render方法(虚拟DOM节点)的输出。

  6. 由于这是React第一次渲染元素,所以React将与浏览器进行通信(代表我们使用DOM API)来显示元素。这个过程通常称为装载。

  7. 然后,React调用另一个componentDidMount生命周期方法。我们可以使用这种方法,例如,在DOM上做一些我们现在知道在浏览器中存在的东西。在此生命周期方法之前,我们处理的DOM都是虚拟的。

  8. 一些组件故事在这里结束。出于各种原因,其他组件可以从浏览器DOM中解除装载。在后一种情况发生之前,React调用另一种生命周期方法componentWillUnmount。

  9. 任何已装载元件的状态可能会更改。该元素的父代可能会重新渲染。在任一种情况下,装载的元件可能会接收不同的属性。这里的魔法发生在这里,我们实际上开始需要React!在此之前,我们根本就不需要React。

  10. 这个组件的故事继续下去,但在它之前,我们需要了解我所说的这个状态。


基础 #7:React组件可以具有私有状态


以下也仅适用于类组件。有没有人提到有些人把表演式的组件叫做哑巴?


状态类字段是任何React类组件中的特殊字段。React监视每个组件状态以进行更改。但是对于React这样做有效,我们必须通过我们需要学习的另一个React API事件来更改状态字段this.setState:


Example 13 – setState API https://jscomplete.com/repl?j=H1fek2KH-


class CounterButton extends React.Component {

  state = {

    clickCounter: 0,

    currentTimestamp: new Date(),

  };

 

  handleClick = () => {

    this.setState((prevState) => {

     return { clickCounter: prevState.clickCounter + 1 };

    });

  };

 

  componentDidMount() {

   setInterval(() => {

     this.setState({ currentTimestamp: new Date() })

    }, 1000);

  }

 

  render() {

    return (

      <div>

        <button onClick={this.handleClick}>Click</button>

        <p>Clicked: {this.state.clickCounter}</p>

        <p>Time: {this.state.currentTimestamp.toLocaleString()}</p>

      </div>

    );

  }

}


// 使用它

ReactDOM.render(<CounterButton />, mountNode);


这是了解最重要的例子。它将基本完成您对“基本法”的基本知识。在这个例子之后,还有一些你需要学习的小事情,但是从这一点来看,它主要是你和你的JavaScript技能。


我们来看一下实例13,从类字段开始。它有两个。特殊state字段被初始化与持有的对象clickCounter 以0开始,并且currentTimestamp 以new Date()开始。


第二类字段是一个handleClick 函数,我们传递给render方法中的button元素的onClick事件。该handleClick 方法使用setState修改此组件实例状态。注意到这一点。


我们正在修改状态的另一个地方在我们在componentDidMount l生命周期方法内部启动的间隔定时器中。它每秒钟执行另一个调用this.setState.。


在render方法中,我们使用了正常读取语法对状态的两个属性。没有专门的API。


现在,请注意,我们使用两种不同的方式更新了状态:


  1. 传递返回一个对象的函数。我们在handleClick 函数中做了这个。

  2. 通过传递一个常规对象。我们在间隔回调中做到了。


这两种方式都是可以接受的,但是当您同时读取和写入状态时,首先是首选的(我们这样做)。在间隔回调期间,我们只写给状态,而不是读取它。当有疑问时,始终使用第一个函数参数语法。它竞争条件更安全,因为setState 实际上是一种异步方法。


我们如何更新状态?我们返回一个具有我们要更新的新值的对象。注意在两次调用中setState,,我们只是从状态字段传递一个属性,而不是两者。这是完全可以的,因为setState实际上将您传递的内容(函数参数的返回值)与现有状态合并。因此,在调用时不指定属性setState意味着我们不希望更改该属性(而不是删除它)。


基础 #8:React会反应


React从它对状态变化做出的事实(虽然不是反应性的,而是按计划)。有一个笑话,React应该被命名为 Schedule!


然而,当任何组件的状态更新时,我们用肉眼看到的是,React对该更新做出反应,并自动反映浏览器DOM中的更新(如果需要)。


将渲染函数的输入视为两者


  • 由父母传递的属性

  • 可以随时更新的内部私有状态


当render函数的输入变化时,其输出可能会改变。


React保留了渲染历史的记录,当它看到一个渲染与前一个渲染不同时,它将计算它们之间的差异,并有效地将其转换为在DOM中执行的实际DOM操作。


基础 #9:React是您的代理


您可以将React视为我们聘请的与浏览器通信的代理。以上面的当前时间戳显示为例。我们不是手动去浏览器并调用DOM API操作来每秒查找和更新p#timestamp元素,而是在组件的状态上更改了一个属性,而React则代表我们与浏览器通信。我相信这是React流行的真正原因。我们讨厌浏览器(和所说的DOM语言的很多方言),React自愿为我们做所有的谈话,免费!


基础 #10:每个React组件都有一个故事(第2部分)


现在我们知道一个组件的状态,以及当这个状态改变了一些魔法的时候,让我们来学习关于该过程的最后几个概念。


  1. 组件可能需要在其状态更新时或者当其父级决定更改传递给组件的属性时重新渲染

  2. 如果后者发生,React会调用另一个生命周期方法componentWillReceiveProps。

  3. 如果状态对象或传入属性被更改,则React有一个重要的决定。组件应该在DOM中更新吗?这就是为什么它在这里调用另一个重要的生命周期方法shouldComponentUpdate。这个方法是一个实际的问题,所以如果你需要定制或自己进行优化渲染过程中,你必须回答返回这个问题无论是true还是false。

  4. 如果没有自定义shouldComponentUpdate 指定,React默认是一个非常聪明的事情,在大多数情况下实际上足够好。

  5. 首先,React此时调用另一个生命周期方法componentWillUpdate。然后React将计算新的渲染输出并将其与最后渲染的输出进行比较。

  6. 如果渲染的输出完全相同,React什么都不做(不需要和浏览器交谈)。

  7. 如果有差异,则React会将浏览器的差异,就像我们之前看到的那样。

  8. 无论如何,由于更新过程无论如何发生(即使输出完全一样),React调用最终的生命周期方法componentDidUpdate。


生命周期方法实际上是逃避舱口。如果你没有做任何特别的事情,你可以创建没有他们的完整的应用。它们非常方便地分析应用中发生的情况,并进一步优化了React更新的性能。


仅此而已。相信与否,上面你学到了什么(或者部分内容,真的),你可以开始创建一些有趣的React应用。



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

 
前端大全 更多文章 从前端小白到技术专家,这里有3点可执行的建议 跟随 Google 工程师夯实前端基础,是种怎样的体验? 趣图:程序员的日常之一 前端跨域知识总结:15篇前端热文回看 JavaScript 有个 Unicode 的天坑
猜您喜欢 DevOps的未来是“装配线” Redis加持,不要错过招行信用卡的这个活动 计算机程序的思维逻辑 (1) PHP核心知识要点 一 ​iOS 中的设计模式 (Swift 版本)