去年,我写了一本关于学习React.js的小书,原来是大约100页。 今年我要挑战本身,把它概括为一篇文章。react
本文不会涵盖什么是React,或者为何要学习它。 相反,这是面向已经熟悉JavaScript并熟悉DOM API基础知识的人,对React.js的基础知识的介绍。程序员
如下全部代码示例均标示为参考。 它们纯粹是为了提供概念而写的例子。 他们大多数能够写得更好一些。express
React是围绕可重用组件的概念设计的。 您定义小组件,并将它们放在一块儿造成更大的组件。数组
全部小或小的组件均可重复使用,甚至跨不一样的项目。浏览器
一个React组件(以其最简单的形式)是一个简单的JavaScript函数:安全
// Example 1 // https://jscomplete.com/repl?j=Sy3QAdKHW function Button (props) { // Returns a DOM element here. For example: return <button type="submit">{props.label}</button>; } // To render the Button component to the browser ReactDOM.render(<Button label="Save" />, mountNode)
用于按钮标签的花括号将在下面介绍。 如今没必要要担忧他们。 ReactDOM也将在后面解释,可是若是要测试这个例子和接下来的代码示例,render
函数就是你须要的。微信
ReactDOM.render
的第二个参数是React将要接管和控制的目标DOM元素。 在jsComplete REPL中,您就可使用mountNode
变量。架构
关于示例1的注意事项有如下几点:app
props
。建立功能组件,你能够经过使用任意名称命名props
。上面的示例1能够用纯粹的React.js来编写,而不须要JSX,以下所示:dom
// Example 2 - React component without JSX // https://jscomplete.com/repl?j=HyiEwoYB- function Button (props) { return React.createElement( "button", { type: "submit" }, props.label ); } // To use Button, you would do something like ReactDOM.render( React.createElement(Button, { label: "Save" }), mountNode );
createElement
函数是React顶级API中函数。 您须要学习的这个级别中共有7件事情中的1项。 可见ReactApi多么简短。
很像DOM自己有一个document.createElement
函数来建立一个由标签名称指定的元素,React的createElement
函数是一个更高级别的函数,能够作相似于document.createElement
的功能。 但它也能够用于建立一个表示React组件的元素。 当咱们使用上面的例2中的Button组件时,咱们这里就是建立了一个React组件。
与document.createElement
不一样,React的createElement
能够接受第二个参数以后的动态参数,以表示建立的元素的后代。 因此createElement
实际上建立一个树。
这是一个例子:
/ Example 3 - React’s createElement API // https://jscomplete.com/repl?j=r1GNoiFBb const InputForm = React.createElement( "form", { target: "_blank", action: "https://google.com/search" }, React.createElement("div", null, "Enter input and click Search"), React.createElement("input", { name: "q", className: "input" }), React.createElement(Button, { label: "Search" }) ); // InputForm uses the Button component, so we need that too: function Button (props) { return React.createElement( "button", { type: "submit" }, props.label ); } // Then we can use InputForm directly with .render ReactDOM.render(InputForm, mountNode);
关于以上例子要注意的几点:
InputForm
不是React组件; 它只是一个React元素。 这就是为何咱们直接在ReactDOM.render
调用中使用它,而不是使用<InputForm />
。React.createElement
调用,由于它都是JavaScript。React.createElement
的第二个参数能够是null,也能够是一个空对象,当元素不须要attributes和props时。上面的代码是您在引入React库时了解的内容。 浏览器不处理任何JSX业务。 然而,咱们人类喜欢看HTML而且使用HTML而不是这些createElement
调用(想象一下使用document.createElement
构建一个网站,我相信你能够的!)。 这就是为何存在JSX的缘由。 咱们能够用很是相似于HTML的语法编写它,而不是用React.createElement
调用上面的表单:
// Example 4 - JSX (compare with Example 3) // https://jscomplete.com/repl?j=SJWy3otHW const InputForm = <form target="_blank" action="https://google.com/search"> <div>Enter input and click Search</div> <input name="q" className="input" /> <Button label="Search" /> </form>; // InputForm "still" uses the Button component, so we need that too. // Either JSX or normal form would do function Button (props) { // Returns a DOM element here. For example: return <button type="submit">{props.label}</button>; } // Then we can use InputForm directly with .render ReactDOM.render(InputForm, mountNode);
关于上面的例子注意如下几点
咱们上面写的(例4)是JSX。 然而,咱们在浏览器的执行版本是它的编译版本(示例3)。 为了实现这一点,咱们须要使用预处理器将JSX版本转换为React.createElement
版本。
那就是JSX。 这是一个折中,容许咱们以相似于HTML的语法编写咱们的React组件,这是一个很好的共识。
上面标题中的“Flux”一词被选为韵脚(...),但它也是Facebook流行的很是受欢迎的应用程序架构的名称。 最着名的实现是Redux。
JSX,顺便说一下,能够本身在其余地方使用。 这不是只有在React中才可使用的。
在JSX部分中,您能够在一对花括号内使用任何JavaScript表达式。
// Example 5 - Using JavaScript expressions in JSX // https://jscomplete.com/repl?j=SkNN3oYSW const RandomValue = () => <div> { Math.floor(Math.random() * 100) } </div>; // To use it: ReactDOM.render(<RandomValue />, mountNode);
任何JavaScript表达式均可以放在那些花括号内。 这至关于JavaScript模板文字中的$ {}
插值语法。
这是JSX中惟一的约束:只有表达式。 因此,你不能使用常规的if语句,可是三元表达式是能够的。
JavaScript变量也是表达式,因此当组件接收到props
列表(RandomValue组件没有,props
是可选的)时,能够在花括号内使用这些props
。 咱们在上面的Button组件中这样作了(示例1)。
JavaScript对象也是表达式。 有时候,咱们在一个花括号里面使用一个JavaScript对象,这使得它看起来像双花括号,但它实际上只是一个大括号内的一个对象。 一个用例是将CSS样式对象传递给React中的style属性:
// Example 6 - An object passed to the special React style prop // https://jscomplete.com/repl?j=S1Kw2sFHb const ErrorDisplay = ({message}) => <div style={ { color: 'red', backgroundColor: 'yellow' } }> {message} </div>; // Use it: ReactDOM.render( <ErrorDisplay message="These aren't the droids you're looking for" />, mountNode );
请注意,我如何仅解析props
参数中的message的。 这是JavaScript。 还要注意上面的style属性是一个特殊的属性(再次,它不是HTML,它更接近于DOM API)。 咱们使用一个对象做为style属性的值。 该对象定义了样式,就像咱们使用JavaScript同样(由于确实就是)。
甚至能够在JSX中使用React元素,由于这也是一个表达式。 记住,一个React元素就是一个函数调用:
const MaybeError = ({errorMessage}) => <div> {errorMessage && <ErrorDisplay message={errorMessage} />} </div>; // The MaybeError component uses the ErrorDisplay component: const ErrorDisplay = ({message}) => <div style={ { color: 'red', backgroundColor: 'yellow' } }> {message} </div>; // Now we can use the MaybeError component: 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等)。 再次声明缘由是由于它们返回的是表达式:
// Example 8 - Using an array map inside {} // https://jscomplete.com/repl?j=SJ29aiYH- const Doubler = ({value=[1, 2, 3]}) => <div> {value.map(e => e * 2)} </div>; // Use it ReactDOM.render(<Doubler />, mountNode);
请注意,我是如何给value
props默认值的,由于它全是Javascript。 还要注意,我在div中输出了一个数组表达式,这在React中是可行的。 它将把每个双倍的值放在一个文本节点中。
简单的功能组件很是适合简单的需求,但有时咱们须要更多的功能。 React支持经过JavaScript类语法建立组件。 这是使用类语法编写的Button组件(在示例1中):
// Example 9 - Creating components using JavaScript classes // https://jscomplete.com/repl?j=ryjk0iKHb class Button extends React.Component { render() { return <button>{this.props.label}</button>; } } // Use it (same syntax) ReactDOM.render(<Button label="Save" />, mountNode);
类语法很简单。 定义一个扩展了React.Component基类的类(须要学习的另外一个顶级的React API)。 该类定义一个惟一实例函数render(),该render函数返回虚拟DOM对象。 每次咱们使用上面的基于Button类的组件(例如,经过执行<Button ... />),React将从这个基于类的组件中实例化一个对象,并在DOM树中使用该对象。
这就是为何咱们在上面的渲染输出中在JSX中使用this.props.label
的缘由。 由于每一个组件都得到一个称为props
的特殊实例属性,该实例属性在实例化时保存传递给该组件的全部值。
// Example 10 - Customizing a component instance // 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>; } } // Use it ReactDOM.render(<Button label="Save" />, mountNode);
咱们还能够定义类属性函数,并在咱们想使用的地方使用,包括返回的JSX输出内:
// Example 11 — Using class properties // 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中新建的类字段语法编写的。这种语法仍然属于stage-2,
,但因为不少缘由,它是访问组件安装实例(因为箭头功能)的最佳选择。 可是,您须要使用像Babel这样的编译器来配置它来理解stage-2
,(或类字段语法)来获取上面的代码。 jsComplete REPL具备预配置。ClickCounter
实例变量。 这容许咱们彻底跳过使用类构造函数调用。handleClick
函数指定为特殊的onClick
,React属性的值时,咱们没有调用它。 咱们把handleClick函数引用传递给出去了。 在这个属性里面调用函数是使用React最多见的错误之一。// Wrong: onClick={this.handleClick()} // Right: onClick={this.handleClick}
在React元素中处理事件时,与DOM API的方式有两个很是重要的区别:
onClick
,而不是onclick
。onClick = {handleClick}
,而不是onClick =“handleClick”
。使用本身的对象将DOM事件对象包装起来,以优化事件处理的性能。 可是在事件处理程序中,咱们仍然能够访问DOM事件对象上可用的全部方法。 React将包装的事件对象传递给每一个句柄调用。 例如,为了防止表单从默认提交操做中,您能够执行如下操做:
// Example 12 - Working with wrapped events // 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> ); } } // Use it ReactDOM.render(<Form />, mountNode);
如下仅适用于类组件(扩展为React.Component的组件)。 函数组件有一个略有不一样的故事。
this.props
访问的props
。 那些props
正是咱们在上面的步骤2中传递的。render
方法(虚拟DOM节点)的输出。componentDidMount
。 咱们可使用这种方法作一些事情,例如,在DOM上作一些咱们如今知道在浏览器中支持处理的东西。 在今生命周期方法以前,咱们处理的DOM所有是虚拟的。componentWillUnmount
。props
。 这里的魔法发生了,咱们如今开始须要React了! 在此以前,咱们彻底不须要作任何事情如下也仅适用于类组件。 有没有人提到有些人把只作展示的组件叫作哑吧?
状态类字段是任何React类组件中的特殊字段。 React监视每一个组件状态以进行更改。 可是对于React要有效地执行这些操做,咱们必须经过另外一个须要学习的React API函数来更改state字段,this.setState
:
// Example 13 - the 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> ); } } // Use it ReactDOM.render(<CounterButton />, mountNode);
这是了解state
最重要的例子。 它将完善您对React交互方式的基础知识。 在这个例子以后,还有一些你须要学习的小事情,可是从这一点来看,它主要是你和你的JavaScript技能。
咱们来看一下实例13,从类字段开始。 它有两个。 特殊状态字段被初始化为一个对象,该对象包含起始值为0的clickCounter
,以及起始值为new Date()
的currentTimestamp
。
第二个类字段是一个handleClick
函数,咱们传递给render方法中的button元素的onClick
事件。 handleClick
方法使用setState
修改此组件实例状态。 注意到这一点。
咱们在componentDidMount
生命周期方法内部启动的间隔定时器中修改状态。 它每秒钟打勾并执行调用this.setState
。
在render方法中,咱们使用了正常读取语法对state两个属性的读取。 没有特殊的API。
如今,请注意,咱们使用两种不一样的方式更新了状态:
handleClick
函数中实现了这部份内容。这两种方式都是能够接受的,可是当您同时读取和写入状态时,第一个是首选的(咱们这样作)。 在间隔回调以内,咱们只写给状态,而不是读取它。 当两难时,始终使用第一个函数参数语法。 它更加安全,由于setState
其实是一个异步方法。
咱们如何更新状态? 咱们返回一个包含咱们要更新的值的对象。 注意在两次调用setState
中,咱们只是从state
字段传递一个属性,而不是二者。 这是彻底能够的,由于setState
实际上将您传递的内容(函数参数的返回值)与现有状态合并。 所以,在调用setState
时不指定属性意味着咱们不但愿更改该属性(而不是删除它)。
React从它对状态变化作出响应的事实(虽然不是反应性的,而是按计划进行)而得名。 有一个笑话,反应应该被命名为Schedule!
然而,当任何组件的状态被更新时,咱们用肉眼看到的是React对该更新作出反应,并自动反映浏览器DOM中的更新(若是须要)。
将render函数输入视为二者
props
当渲染功能的输入变化时,其输出可能会改变。
React保留了渲染历史的记录,当它看到一个渲染与前一个渲染不一样时,它将计算它们之间的差别,并将其有效地转换为在DOM中执行的实际DOM操做。
您能够将React视为咱们聘请的与浏览器通讯的代理。 以上面的当前时间戳显示为例。 咱们不是手动去浏览器并调用DOM API操做来每秒查找和更新p#timestamp元素,而是在组件状态上更改了一个属性,而React表明咱们与浏览器进行通讯。 我相信这是真正受欢迎的真正缘由。 咱们讨厌浏览器(domApi很繁琐),React自愿为咱们作全部对接工做,免费!
如今咱们知道一个组件的状态,以及当这个状态改变了一些魔法的时候,让咱们来学习关于该过程的最后几个概念。
props
时,该组件可能须要从新呈现componentWillReceiveProps
。props
被更改,则React有一个重要的决定。 组件应该在DOM中更新吗? 这就是为何它在这里调用另外一个重要的生命周期方法,shouldComponentUpdate
。 这个方法是一个实际的问题,因此若是你须要本身定制或优化渲染过程,你必须经过返回true或false来回答这个问题。customComponentUpdate
,React默认是一个很是聪明的事情,在大多数状况下实际上足够好。componentWillUpdate
。 而后React将计算新的渲染输出并将其与最后渲染的输出进行比较。componentDidUpdate
。生命周期方法其实是舱口。 若是你没有作任何事情,你能够建立没有他们的完整的应用程序。 他们能够用来很是方便地分析应用程序中发生的状况,并进一步优化了React更新的性能。
根据以上学到的东西(或其中的一部分,真的),您就能够开始建立一些有趣的React应用程序。 若是您渴望了解更多信息,请访问咱们的Plactsight的React.js课程入门:
翻译自All the fundamental React.js concepts, jammed into this single Medium article
关注个人公众号,更多优质文章定时推送
建立了一个程序员交流微信群,你们进群交流IT技术
若是已过时,能够添加博主微信号15706211347,拉你进群