5

本章是翻译的React启蒙系列的第四章,主要将讲述如何使用纯JavaScript语句创建React节点,本章内容依旧非常基础,通过阅读本章内容你将了解React nodes的定义,React.createElement()所需的各参数的实际意义以及部分React事件相关知识。

什么是React nodes?

定义:

React node是一种轻量的,无状态的,不可变的,真实DOM节点的一种虚拟代表。它是React创建的基本元素。

这种虚拟代表被称作Virtual DOM,简言之,React使用React nodes创建虚拟DOM,一个完整的React组件最终可用来创建真实的DOM(或其它结构(如React Native))。

React node可以使用纯JavaScript方式创建也能使用JSX创建,本章我们先详细探讨如何使用纯JavaScript创建React node,这对之后更好的理解JSX很有帮助。本章内容基础,但是对理解React非常重要。

创建React nodes

调用React.createElement(type,props,children)函数就可以创建一个React node,这个方法的使用类似创建真实DOM节点的方法,下面详细看看这个函数的各个参数

  • type (string |React.createClass() ):可以是一个代表HTML元素的字符串,也可以是一个React组件实例(React.createClass()的实例);

  • props(null|object):可以为null,也可以是一个对象;

  • Children(null | string | React.createClass() | React.createElement()):可以为null,如果是Text,其将被转换为文本节点,也可以是一个React node实例 (React.createElement() )或一个React 组件实例 (React.createClass() )。

下面是我用这个函数创建了一个React<li>节点,其中的文本为one,其idli1

var reactNodeLi = React.createElement('li', {id:'li1'}, 'one');

正如前面所说,该函数第一个参数代表你想创建的节点类型,第二个参数代表给该节点传入的参数(props),第三个代表该React节点的子节点(文本,子元素节点或组件实例)。

为了将此节点 (reactNodeLi )渲染入DOM中,我还需要调用ReactDOM.render()方法,代码如下:

//<div id="app"></div>
ReactDOM.render(reactNodeLi,document.getElementById('app'));

其实上面这句代码在执行过程中做了以下一些事情:

  • 生成一个由React nodes组成的Virtual DOM;

  • 利用该Virtual DOM构建一个真实的DOM分支;

  • <div id="app"></div>处,将该真实DOM分支插入真实DOM中,并当做所插入处的子节点;

真实的DOM如下

//渲染前
<div id="app"></div>

//渲染后
<div id="app">
    <li id="li1" data-reactid=".0">one</li>
</div>

以上是使用React.createElement()基础的例子,使用这种方法,我们也可以创建复杂的结构,下面我将使用此方法模拟html的无序列表(<ul>

// 创建React元素 <li>'
var rElmLi1 = React.createElement('li', {id:'li1'}, 'one');
var rElmLi2 = React.createElement('li', {id:'li2'}, 'two');
var rElmLi3 = React.createElement('li', {id:'li3'}, 'three');

//创建React元素<ul>,并将<li>包含其中
var reactElementUl = React.createElement('ul', {className:'myList'}, rElmLi1,rElmLi2,rElmLi3);

渲染之前,我想展示另外一种创建方式,这种方式用React.createElement()代替了变量rElmLi*

var reactElementUl = React.createElement(
    'ul', {
        className: 'myList'
    },
        React.createElement('li', {id: 'li1'},'one'),
        React.createElement('li', {id: 'li2'},'two'),
        React.createElement('li', {id: 'li3'},'three')
);

上述代码的渲染结果如下:

<ul class="myList" data-reactid=".0">
    <li id="li1" data-reactid=".0.0">one</li>
    <li id="li2" data-reactid=".0.1">two</li>
    <li id="li3" data-reactid=".0.2">three</li>
</ul>

你可以在JSfiddle中细细体味上述代码 点击JSFiddle查看更多

React nodes是一个树形的JavaScript对象,其使用Virtual DOM的形式来表征真实DOM。Virtual DOM随后会在Html页面中被渲染为真实的DOM分支。

本节笔记

  • React.createElement(type,props,children)方法中第一个参数type可以是一个代表真实Html 元素的字符串 (如"li" ),也可以是一个自定义元素 (如"foo-bar" ),还可以是一个React组件实例 (如React.createClass()的实例 )

  • 以下是React支持的标准Html元素

    a abbr address area article aside audio b base bdi bdo big blockquote body br
    
    button canvas caption cite code col colgroup data datalist dd del details dfn
    
    dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
    
    h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li
    
    link main map mark menu menuitem meta meter nav noscript object ol optgroup
    
    option output p param picture pre progress q rp rt ruby s samp script section
    
    select small source span strong style sub summary sup table tbody td textarea
    
    tfoot th thead time title tr track u ul var video wbr
    

渲染React nodes到真实的DOM

React提供一个名为ReactDOM.render()方法用以将React nodes渲染到真实的DOM中(此方法存在于react-dom.js文件中)。

下面的例子中,我们使用ReactDOM.render()方法,把React 节点<li><foo-bar>渲染到了DOM中 点击JSFiddle查看更多

渲染入真实DOM后的HTML代码如下

<body>
    <div id="app1"><li class="bar" data-reactid=".0">foo</li></div>
    <div id="app2"><foo-bar classname="bar" children="foo" data-reactid=".1">foo</foo-bar></div>
</body>

ReactDOM.render()做了两件事:渲染React nodes为Virtual DOM;渲染其为真实DOM。

本节还有以下需要注意的点

  • 当把React nodes渲染进某一真实DOM节点中时,会清除该真实DOM节点的所有子元素;

  • ReactDOM.render()只是React渲染React nodes到真实DOM中的一种方法,还存在别的渲染方法,比如说你可以在服务器端通过ReactDOMServer.renderToString()方法将React nodes渲染为节点;

  • 当子节点有改变时(依据diff算法),React会重新渲染React nodes到相同的DOM中。

理解props

传入React.createElement(type,props,children)方法的第二个参数是一个包含键值对的对象(props)。

props主要有以下几个作用

  1. 如果prop中某个键与一个已知的Html属性名相同,在最终渲染生成的HTML元素中,其值会作为该元素的该属性的值;

  2. prop可以被用来储存值,用以传递给 React 创建元素或组件;

  3. 一些特殊的props具有特殊的用途 (key,ref,Dangerously Set innerHTML )

总的来说,你可以把props看做React nodes的配置值,也可以把props当做 React 元素的属性值。

下例中,我为React的<li>元素传入了五个props,其中foo:'bar'不是标准的HTML属性,其余都是标准的HTML属性。

var reactNodeLi = React.createElement('li',
{
    foo:'bar',
    id:'li1',
    
    //class 表示为className
    //i.e., className
    className:'blue',
    'data-test':'test',
    'aria-test':'test',

    //CSS代码采用驼峰式
    //i.e., backgroundColor
    style:{backgroundColor:'red'}
},
'text'
);

渲染结果如下

<li id="li1" 
    data-test="test" 
    class="blue" 
    aria-test="test" 
    style="background-color:red;" 
    data-reactid=".0">
    text
</li>

键名为标准的HTML属性的props项,被渲染后其值是对应属性的值标准的HTML属性,而foo并非标准HTML属性,因此foo并未在渲染后的真实DOM中有所表现,不过这个值可以通过下面的方法读取。点击JSFiddle查看更多

通过这两个例子,你肯定近一步理解props了。

关于props你还应该了解的事情

  • React中值为空白的props,渲染后其值为true (比如说id=""渲染后为id="true",test渲染后为test="true" );

  • props中如果同一属性出现两次,后者的值将生效;

  • props中被传入的标准React 元素属性(HTML中真实存在的元素的属性),渲染后依旧是该元素的对应属性值,非标准元素的属性将不会被渲染,如果传入的是一个自定义元素,那么其所有的属性都将被渲染如<x-my-component custom-attribute="foo" />;

  • React中class属性写作className,for写作htmlFor,style属性为写作驼峰式;

  • HTML表单元素 (<input><textarea></textarea>等),当其由React创建时,其支持与交互有关的属性valuecheckedselected等。

  • keyrefdangerouslySetInnderHtml属性不存在于真实DOM中,它们在React中有独特的作用;

  • React中所有的属性都被写作驼峰式(如accept-charset写做acceptCharset);

  • 以下是React支持的一些属性

    accept acceptCharset accessKey action allowFullScreen allowTransparency alt
    
    async autoComplete autoFocus autoPlay capture cellPadding cellSpacing challenge
    
    charSet checked classID className colSpan cols content contentEditable
    
    contextMenu controls coords crossOrigin data dateTime default defer dir
    
    disabled download draggable encType form formAction formEncType formMethod
    
    formNoValidate formTarget frameBorder headers height hidden high href hrefLang
    
    htmlFor httpEquiv icon id inputMode integrity is keyParams keyType kind label
    
    lang list loop low manifest marginHeight marginWidth max maxLength media
    
    mediaGroup method min minLength multiple muted name noValidate nonce open
    
    optimum pattern placeholder poster preload radioGroup readOnly rel required
    
    reversed role rowSpan rows sandbox scope scoped scrolling seamless selected
    
    shape size sizes span spellCheck src srcDoc srcLang srcSet start step style
    
    summary tabIndex target title type useMap value width wmode wrap

在React中使用内联样式

想要在React中使用内联样式,只需要在React 节点中传入style属性,并把一个包含CSS属性和对应值的对象赋值给该属性即可。

以下例子可以让你清楚认识这一点。

var inlineStyles = {backgroundColor:'red', fontSize:20};
var reactNodeLi = React.createElement ('div',{style:inlineStyles}, 'styled' )

ReactDOM.render (reactNodeLi, document.getElementById ('app1' ) ); 

上述代码编译后的结果如下:

<div id="app1"> 
<div style="background-color:red;font-size:20px;" data-reactid=".0">styled</div>
</div>

上述代码有两点值得注意的

  1. fontsize属性值后,我并未加"px",做为单位,“px”是React内联样式的默认单位,如果用其他的单位可以类似"2em"这样写,用引号围起来就可以了;

  2. 在JavaScript中写内联样式时需要使用小写驼峰式(backgroundColor);

本节笔记

  • 有前缀的属性,字母装换为大写 (-后的第一个字母大写 );

  • 其实在JavaScript中,CSS样式一直都是采用驼峰式的,React也延续了这一习惯;

  • 除了以下CSS属性外,React数值的默认单位都是"px"

columnCount fillOpacity flex flexGrow flexShrink fontWeight lineClamp lineHeightopacity order orphans strokeOpacity widows zIndex zoom

React元素工厂

React 提供一种名为React元素工厂(React element factories)的方法来快速创建React元素。

官方定义:一个 ReactElement 工厂就是一个简单的函数,该函数生成一个带有特殊 type 属性的 ReactElement

其使用方法如下:

//uses
React.DOM.li(props, children);
var reactNodeLi = React.DOM.li({id:'li1'}, 'one');

对比一下我们之前用的方法能让你更清楚

// 使用React.createElement(type, prop, children);
var reactNodeLi = React.createElement('li', {id:'li1'}, 'one');

以下是React提供的所有的内置元素工厂方法

jsa,abbr,address,area,article,aside,audio,b,base,bdi,bdo,big,blockquote,
body,br,button,canvas,caption,cite,code,col,colgroup,data,datalist,dd,
del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,
form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,img,input,ins,
kbd,keygen,label,legend,li,link,main,map,mark,menu,menuitem,meta,meter,
nav,noscript,object,ol,optgroup,option,output,p,param,picture,pre,progress,
q,rp,rt,ruby,s,samp,script,section,select,small,source,span,strong,style,
sub,summary,sup,table,tbody,td,textarea,tfoot,th,thead,time,title,tr,track,
u,ul,var,video,wbr,circle,clipPath,defs,ellipse,g,image,line,linearGradient,
mask,path,pattern,polygon,polyline,radialGradient,rect,stop,svg,text,tspa

本节笔记

  • 如果你使用JSX,你可能永远不会用到ReactElement工厂;

  • React 有一个内置的辅助方法用于创建工厂函数。事实上该方法就是这样的:React.createFactory (type),这个函数用于创建工厂函数,利用这个创建好的函数可以方便的创建其实例。

function createFactory(type)
    { return React.createElement.bind(null, type);
}

var div = React.createFactory('div');
var root = div({ className: 'my-div' });

React.render(root, document.body);`

React中的事件

在React中添加事件和在DOM中添加事件一样方便,在下面的例子中,我把clickmouseover事件绑定在了一个Reactdiv节点上。

点击JSFiddle查看更多

var mouseOverHandler = function mouseOverHandler() {
        console.log('you moused over');
    };

var clickhandler = function clickhandler() {
        console.log('you clicked');
    };

var reactNode = React.createElement(
        'div',
        { onClick: clickhandler, onMouseOver: mouseOverHandler },//在此绑定事件
        'click or mouse over'
    );

ReactDOM.render(reactNode, document.getElementById('app'));

通过on就可以绑定对应事件

React为每一个事件绑定了一个被称为SyntheticEvent的对象,里面包含了该事件的所有细节,其实这个和DOM事件很类似,某个事件的SyntheticEvent实例,可以通过事件的回调函数访问,如下例。

点击JSFiddle查看更多

var clickhandler = function clickhandler(SyntheticEvent) {
        console.log(SyntheticEvent);
    };

var reactNode = React.createElement(
        'div',
        { onClick: clickhandler},
        'click'
    );

ReactDOM.render(reactNode, document.getElementById('app'));

每一个syntheticEvent对象实例中都包含了以下属性

bubbles

cancelable

DOMEventTarget currentTarget

defaultPrevented

eventPhase

isTrusted

DOMEvent nativeEvent

void preventDefault()

isDefaultPrevented()

void stopPropagation()

isPropagationStopped()

DOMEventTarget target

timeStamp

type

此外一些事件的syntheticEvent还具有一些特有属性,比如说onClick还具有以下属性

altKey

button

buttons

clientX

clientY

ctrlKey

getModifierState(key)

metaKey

pageX

pageY

DOMEventTarget relatedTarget

screenX

screenY

shiftKey

下表按类别列出了事件的syntheticEvent的特有属性

点击表格查看

关于React中的事件还需要注意以下几点

  • React中的各事件已经规范化,你可以放心的跨浏览器使用;

  • React事件默认在事件冒泡阶段(bubblling)触发,如果想在事件捕获阶段触发需要在事件名后加上Capture(如onClick变为onClickCapture);

  • 如果你想获知浏览器事件的详情,你可以通过在回调函数中查看SyntheticEvent对象中的nativeEvent值;

  • React实际上并未直接为React nodes添加事件,它使用了event delegation事件委托机制

  • 想要阻止事件冒泡,需要手动调用e.stopPropagation()e.preventDefault(),不要直接使用returning false,

  • React其实并没有支持所有的JS事件,不过它还提供额外的生命周期函数以供使用React lifecycle methods.

参考

全书
原文


zhangwang
8k 声望1.8k 粉丝

前端,摄影,阅读,好奇