核心提示
这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程。固然,还有我本身尝试的实例。往后还将对官方文档进阶和高级部分分专题进行学习并记录。
尽管前端学习面临着各类各样的焦虑,尽管愈来愈多的框架出现,然而无能否认的是,它们都在从不一样的角度提升生产力——从这个角度而言,之因此焦虑,本质缘由是由于行业的门槛实际上是下降了,而本身变得“不值钱”起来。在目前的环境下,不管如何须须认可,学习同样技能不可能让你一生靠它吃饭了。若是真有,那就是原生的,基础的,底层的知识——固然这是旁话了。
假使者互联网的历史是一本薄薄的小册子,那么我常常看到一个历史事实:第一页说今天某个框架很火,有多少人在用,一时间风头无两。但翻到历史的下一页就是一句话:又出来一个新的框架,原来那个竞争不过,就怂了。
因此,给本身打个气吧:什么均可以怂,可是你,别怂了。javascript
语言:基于javascript,同时也涉及了ES6的部分语法,好比箭头函数(Arrow functions)、javascript类(Class>)、模板字符串(Template literals)等。css
笔者操做时基于以下布局。相关文件能够在官网下载到。html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title></title> <link rel="stylesheet" type="text/css" href="css/css.css"/> <!-- 核心 --> <script src="js/react.js"></script> <!-- 加载dom方法 --> <script src="js/react-dom.js"></script> <!-- 将 JSX 语法转为 JavaScript 语法 --> <script src="js/browser.min.js"></script> <!-- 自身的javascript代码 --> <script type="text/javascript" src="js/js.js"></script> </head> <body> <div id="example"></div> <!-- 凡是用到jsx语法的地方type应该为text/babel --> <script type="text/babel"> </script> </body> </html>
上面代码有两个地方须要注意。首先,最后一个 <script>
标签的 type
属性为 text/babel
。这是由于 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"
。前端
其次,上面代码一共用了三个库: react.js
、react-dom.js
和 Browser.js
,它们必须首先加载。其中,react.js
是 React 的核心库,react-dom.js
是提供与 DOM 相关的功能,Browser.js
的做用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
——若是你把react语句写到外链的js里面,chrome没法支持非http协议的跨域读取。因此须要在服务器环境下使用。java
ReactDOM.render 是 React 的最基本方法,render直接理解为“渲染”,“表达”、“表述”也没啥问题。用于将模板转为 HTML 语言,并插入指定的 DOM 节点。react
使用方法以下:
ReactDOM.render(要插入的html内容,选择器)
jquery
ReactDOM.render( <h1>Hello World!</h1>, document.getElementById('example') )
上面代码将一个 h1
标题,插入 example
节点。
渲染模块:React每次渲染只更新有必要更新的地方,而不会更新整个ui。git
ReactDOM.render()方法的第一个参数有点像定义innerHTML,然而你不能这么恶搞:github
<h1>hello world!</h1><h2>hehe</h2>//报错,两个顶层标签
但这样写是能够的:web
<h1>hello <small>world!</small></h1>//经过:只有一个顶层标签
说白了,假设第一个参数是集装箱,那么就是你一次只能放一个箱子!
你能够给标签加上data-xxx或是class(注意,写做className
),id等属性,可是你加style属性又会报错:
<h1>hello <small style="color:red;">world!</small></h1>//有行间样式会报错
style具体怎么写,会在后面说起。
上面第一个参数的代码叫作JSX语法。
所谓JSX语法,既不是javascript里的字符串,也不是html。像是在Javascript代码里直接写XML的语法,每个XML标签都会被JSX转换工具转换成纯Javascript代码,并不加任何引号。
JSX容许你使用它生成React里的元素(Element),JSX能够编译为javascript
React 官方推荐使用JSX, 固然你想直接使用纯Javascript代码写也是能够的,只是使用JSX,组件的结构和组件之间的关系看上去更加清晰。
基本语法规则:容许 HTML 与 JavaScript 的混写。遇到 HTML 标签(以 <
开头),就用 HTML 规则解析;遇到代码块(以 {
开头),就用 JavaScript 规则解析。
这意味着javascript里的语法,方法到了jsx同样适用。
另外一方面,你能够按照xml的结构为JSX里的元素指定子类和属性。
然而须要注意的是,JSX的血缘相对于html,仍是更亲近于javascript,属性仍是得采用驼峰式写法,以前提到元素的class必须写成calssName,就是一个例子。
<h1 class="greeting">Hello world!</h1>
,一般是这样写:var element = ( <h1 className="greeting"> Hello, world! </h1> );
或者这样:
var element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
实际上完整版是这样一个React对象:
var element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
var arr=['hello','React','Vue']; arr.push('Angular'); ReactDOM.render( <div> { arr.map(function(ele){ return <h2>{ele}!</h2> }) } </div>, document.getElementById('example') )
效果将打出4个h2。
这彷佛立刻颠覆了刚刚创建起来关于集装箱每次只能放一个箱子的认知。其实更为正确的理解是:ReactDOM.render()做为一个模板被执行了4次。经过它,能够用较少的语句实现塞4个箱子。
你还能够试试别的javascript语句能不能成。好比:
var arr=['hello','React','Vue','Angular']; var str=arr.join(' '); ReactDOM.render( <div> { <h1>{arr}!</h1> } </div>, document.getElementById('example') )
说明代码块内能够放变量。
这种功能看起来稍显老旧,不如这么作:
var arr=[ <h1 key={0}>hello</h1>, <h2 key={1}>React,</h2>, <h2 key={2}>Vue,</h2>, <h2 key={3}>Angular!!!</h2> ]; ReactDOM.render( <div>{arr}</div>, document.getElementById('example') );
上面代码的arr
变量是一个数组,结果 JSX 会把它的全部成员,添加到模板。
笔者按:没加key值,会提示错误。
key={1.toString()}
,固然你有ID名的话,把id做为key也是推荐的。区别元素(Elemnt)和组件(Component)
元素是组件的组成部分
组件和属性(props)
组件让你UI分割为若干个独立的、可复用的部分。
从概念上讲,组件就像JavaScript函数。 他们接受任意的参数(“props”)并返回React的元素。
组件能够套组件。
理解组件最简单的方法就是写一个函数
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
用ES6语法写成的组件函数是:
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
上面的代码建立一个函数容许你把props值做为参数传进去。而实际上,在react里面有本身封装组件的方法。
React 容许将代码封装成组件(component),而后像插入普通 HTML 标签同样,在网页中插入这个组件。React.createClass 方法,顾名思义,就用于生成一个组件类。
var Message=React.createClass({ render:function(){ return <h1>你是个人 {this.props.name}</h1>; } }); ReactDOM.render( <Message name="小甜甜" />, document.getElementById('example') );
显示为h1标题。
让咱们回顾这个例子:
<Message name="小甜甜" />
为元素。{name="小甜甜}
做为Message组件的props。<h1>你是个人小甜甜</h1>
是这个组件渲染的结果。你能够为这个组件类对象建立多个属性(this.props.xxx
),而后经过给属性赋值来实例化它们.
所谓组件类,由React.createClass 方法生成。他的参数能够理解为一个对象。对象的constructor指向该组件。组件怎么表示呢?首先必须存在一个大写字母开头的变量好比Message里。而后在ReactDOM.render()方法中引用——<Message name="..."/>
。其实是个xml。注意是能够个自闭合标签。
跟以前插入html代码同样,实例内只能有一个顶层标签。
react是很是灵活的,但有一个严格的规则:
全部react组件的行为必须像纯函数,忠于它们的属性。
因而咱们对ReactDOM.render()方法又有了新的认识。到目前为止,ReactDOM.render()方法中,第一个参数必须只有一个顶层,它本质是元素:
对象
的实例
。前面说到,组件类方法React.createClass()
的参数是个对象。你能够用this.props
定义它的各类属性,而这个参数与组件的属性一一对应,可是,有一个例外,就是 this.props.children
属性。说白了就是你的属性用什么英文名都行,除了children
。
所谓children表示组件的全部子节点。
既然是全部属性的集合,不如试试这样恶搞:
var Message=React.createClass({ render:function(){ return <h1>你是个人 {this.props.children}</h1>; } }); ReactDOM.render( <Message name="小甜甜" and="和" xxx="牛夫人" />, document.getElementById('example') );
虽然不报错,但你会发现什么属性都没显示出来。
尝试给这个this.prop.children加.name
或是["name"]
再或是[0]
后缀,都不显示东西。
对children到底是什么类型,比较靠谱的解释是:
这里须要注意,
this.props.children
的值有三种可能:若是当前组件没有子节点,它就是undefined
;若是有一个子节点,数据类型是object
;若是有多个子节点,数据类型就是array
。因此,处理this.props.children
的时候要当心。
React.Children.map()
方法数组确实是强大的功能,有了它能够很快地往箱子里塞东西。同理,React 提供一个工具方法 React.Children
来处理 this.props.children
。咱们能够用 React.Children.map
来遍历子节点,而不用担忧 this.props.children
的数据类型是 undefined
仍是 object
。
React.Children.map()
方法结合this.props.children能够帮助咱们快速地把组件类的对象打出来。
React.Children.map(this.prop.children,function(child){ return {child} })
由于children没有指定对象是谁,因此要把第一个参数改写为xml的形式
var Message=React.createClass({ render:function(){ return <h2>你是个人 { React.Children.map(this.props.children,function(child){ return child//若是你想自动生成span那就应该是<span>{child}<span> }) }</h2>; } }); ReactDOM.render( <Message name="haha" named="hehe">//在此定义name等属性,不会显示。 <span>小甜甜</span> <span>、牛夫人</span> <span>和小亲亲~~</span> </Message>, document.getElementById('example') );
打印出来的结构是:
span也不是必须加的,你彻底能够把render表达式写成:
ReactDOM.render( <Message>小甜甜、牛夫人和小亲亲~~</Message>, document.getElementById('example') );
这是只有一个子节点的状况。而多个子节点能够应用在ul/ol-li这样的体系中。
咱们能够用 React.Children.map 来遍历子节点.
1.React.Children.map
object React.Children.map(object children, function fn [, object context])
在每个直接子级(包含在 children 参数中的)上调用 fn 函数,此函数中的 this 指向 上下文。若是 children 是一个内嵌的对象或者数组,它将被遍历:不会传入容器对象到 fn 中。若是 children 参数是 null 或者 undefined,那么返回 null 或者 undefined 而不是一个空对象。
2.React.Children.forEach
React.Children.forEach(object children, function fn [, object context])
相似于 React.Children.map(),可是不返回对象。
3.React.Children.count
number React.Children.count(object children)
返回 children 当中的组件总数,和传递给 map 或者 forEach 的回调函数的调用次数一致。
4.React.Children.only
object React.Children.only(object children)
返回 children 中仅有的子级。不然抛出异常。
React有一个强大的组合模型,官方建议组件之间多使用组合,而不是继承。
本节将研究如何使用组合作到继承的事。
一些组件在应用以前,可能不知道他们的children。 好比常见的sidebar(组件栏)或对话框。
建议这样的组件经过props传递到子组件,以指望影响它们的输出效果:
var Content=React.createClass({//子组件 render:function(){ return ( <div style={{color:this.props.color}}> {this.props.children} </div> ); } }); var App=React.createClass({//父组件 render:function(){ return ( <Content color="blue"> <h1>欢迎欢迎</h1> <h2>热烈欢迎</h2> </Content> );//return的内容(props)是到用时再定义的 } }); ReactDOM.render( <App/>, document.getElementById('example') );
有的时候或许还须要预留接口,你能够定义:
var Xxx=React.createClass({ render:function(){ return ( <h1>欢迎欢迎</h1> ) } }); var Yyy=React.createClass({ render:function(){ return ( <h2>热烈欢迎</h2> ) } }) var Content=React.createClass({//子组件 render:function(){ return ( <div> {this.props.xxx} {this.props.yyy} </div> ); } }); var App=React.createClass({//父组件 render:function(){ return ( <Content xxx={<Xxx/>} yyy={<Yyy/>}/> );//把子组件和孙组件一次性封装,经过props } }); ReactDOM.render( <App/>, document.getElementById('example') );
在上面这段代码中,Content的属性xxx,和yyy都是能够自定义组件。容许你灵活地放孙级组件。
上面代码中,xxx,yyy都是灵活的,若是你想让它变得不可定义,把它写死就好了。
var Xxx=React.createClass({ render:function(){ return ( <h1>欢迎欢迎</h1> ) } }); var Yyy=React.createClass({ render:function(){ return ( <h2>热烈欢迎</h2> ) } }); var Zzz=React.createClass({ render:function(){ return ( <h3>我是this.props.children</h3> ); } }); var Content=React.createClass({//子组件 render:function(){ return ( <div> {this.props.xxx} {this.props.yyy} {this.props.children} </div> ); } }); var App=React.createClass({//父组件 render:function(){ return ( <Content xxx={<Xxx/>} yyy={<Yyy/>}> <Zzz/> </Content> );//在此代码中<Zzz/>属于props.children } }); ReactDOM.render( <App/>, document.getElementById('example') );
暂时没有发现非使用继承不可的地方。
props和组合给你所须要的灵活性,借此能够明确而安全地定制组件的外观和行为。 记住,组件能够接受任意的props,包括原始值、react元素,或函数。
若是你在组件间复用非ui相关的函数,建议把它提取到一个单独的JavaScript模块。 组件能够调用和使用这个函数,对象,或是类,而没必要去扩展它。
组件的属性能够接受任意值,字符串、对象、函数等等均可以。有时,咱们须要一种机制,验证别人使用组件时,提供的参数是否符合要求,主要用于调试。
笔者按:
以前提到,React.createClass()方法的参数是一个对象,设计者还给它放置了组件类的protoTypes子属性(注意是小写开头!)。个人理解就是存放限制子属性设置的地方。
而这个React.PropTypes
属性(注意是大写开头!),就是用来验证组件实例的属性是否符合要求。
console.log(React.ProtoType),能够看到它的属性都是一个对象,拥有各类方法。
其中最为普遍最多的就是这个isRequire。理解为是“必须的”就能够了。
好比这个例子:
var result=[1,2,3,4] var Message=React.createClass({ propTypes: { sum: React.PropTypes.number.isRequired//调用验证 }, render:function(){ return <h2>1+1= { this.props.sum } </h2> } }); console.log(React.PropTypes) ReactDOM.render( <Message sum={result}/> ,document.getElementById('example') );
输出为1+1=1234
虽然怎么看1234都像数字,但它确实是个数组。即便能显示,可是会报错。
此外,getDefaultProps
方法能够用来设置组件属性的默认值。
这个功能与其说是get不如说是set。当你定义了各类属性以后,能够在该组件prototype下的constructor.defaultProps
找到它们。原来它们在本质上仍是一个object对象。
借助这个框架,你能够快速地封装本身想要的子属性和方法。
var Xxx=React.create({ protoTypes:{ ... }, getDefaultProp:function(){ return { prop1:... prop2:... prop3:... } },//放函数,不要直接放对象! render:function(){ ... } } })
有了它,彷佛能够没必要在行间定义各类属性值了,看起来至关美观。
然而,面临这样一个问题。
var Message=React.createClass({ getDefaultProps:function(){ return { name1:'小亲亲', name2:'小甜甜', name3:'牛夫人' } }, render:function(){ return <h2>你是个人 { this.props.name1 } </h2> } }); //console.log(Message) ReactDOM.render( <Message name1="xxx"/> ,document.getElementById('example') );
打印出的效果是你是个人xxx
。
在constructor.defaultProps
中是找不到的行间定义的name的。
在React的典型数据流中,props是组件和它的子属性交互的惟一方式。每次修改一个child,你就得给再渲染它一次。然而,在个别状况下你须要强行绕过典型数据流修改child,这个child多是组件的实例。也多是DOM的节点,所以React提供了一个修改途径——ref。
React支持特殊的属性ref,你能够附加到任何组件上。 ref属性须要一个回调函数,此函数将组件安装或卸载后当即执行。ref属性用于HTML元素时,ref回调接收底层的DOM元素做为它的参数。
组件并非真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫作虚拟 DOM (virtual DOM)。只有当它插入文档之后,才会变成真实的 DOM 。根据 React 的设计,全部的 DOM 变更,都先在虚拟 DOM 上发生,而后再将实际发生变更的部分,反映在真实 DOM上,这种算法叫作 DOM diff ,它能够极大提升网页的性能表现。
可是,有时须要从组件获取真实 DOM 的节点,这时就要在你须要的节点中插入 ref
属性,而后经过相关事件去定义它,注意,调用时的节点为this.refs.ref名。
一个组件,返回一个文本框和一个按钮。要求点击按钮后,获取文本框节点
var MyComponment=React.createClass({ btnClick:function(){//btnClick是自定义的函数名,用于定义触发事件后实行的方法,调用为this.btnClick this.refs.myTextInput.focus();//获取焦点。 }, turnGreen:function(){//载定义一个改变value值的方法 this.refs.myTextInput.value="我怎么绿了"; this.refs.myTextInput.style.color="green"; }, render:function(){ return ( <div> <input type="text" ref="myTextInput" onFocus="this.turnGreen" />//要求自闭合标签所有写上“/”!不然报错 <input type="button" value="Focus this text input" onClick={this.btnClick} /> </div> ); } }); ReactDOM.render( <MyComponment/> ,document.getElementById('example') );
上面代码中,组件 MyComponent
的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了作到这一点,文本输入框必须有一个 ref
属性,而后 this.refs.[refName]
就会返回这个真实的 DOM 节点。
在获取焦点以后,立刻对其执行turnGreen方法。使得里面的样式变绿。
若是直接在输入框节点中指定value值,结果是只读的。用户没法修改。报错信息以下
react.js:19287 Warning: Failed form propType: You provided a
value
prop to a form field without anonChange
handler. This will render a read-only field. If the field should be mutable usedefaultValue
. Otherwise, set eitheronChange
orreadOnly
. Check the render method ofMyComponment
.
解决思路参见第八章 表单
须要注意的是,因为** this.refs.[refName]
属性获取的是真实 DOM ,因此必须等到虚拟 DOM 插入文档之后,才能使用这个属性,不然会报错。**上面代码中,经过为组件指定 Click
事件的回调函数btnClick
,确保了只有等到真实 DOM 发生 Click
事件以后,才会读取 this.refs.[refName]
属性。
React 组件支持不少事件,除了 Click
事件之外,还有 KeyDown
、Copy
、Scroll
等,完整的事件清单请查看官方文档。
学习了refs以后,你的第一反应就是“用事件触发它发生”。若是真是这样的话,不如花点事件想一想你的组件结构应该有哪些状态(state),显然,每一个组件应该有本身的合适状态。参见组件的生命周期。
组件免不了要与用户互动,React 的一大创新,就是将组件当作是一个状态机,一开始有一个初始状态,而后用户互动,致使状态变化,从而触发从新渲染 UI。
接下来结合ref来作个demo
var ToggleState=React.createClass({ getInitialState:function(){//getInitialState是固有方法 return {check:false}; },//设置一个布尔值状态属性check toggleClick:function(event){ this.setState({ check:!this.state.check });//每次触发就改变布尔值 if(this.state.check){ this.refs.para.innerText='我不喜欢'; this.refs.para.style.color="green"; }else{ this.refs.para.innerText='我喜欢'; this.refs.para.style.color="purple"; } }, render:function(){ return ( <p ref="para" onClick={this.toggleClick}> 你喜欢男人吗?点击切换。 </p> ) } }); ReactDOM.render( <ToggleState/>, document.getElementById('example') );
上面代码是一个 toggleState
组件,它的 getInitialState
方法用于自定义一个check属性并设置其初始状态,能够经过 this.state
属性读取。当用户点击组件,致使状态变化,this.setState
方法修改状态值,每次修改之后,都会自动调用 this.render
方法,再次渲染组件。
因为 this.props
和 this.state
都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props
表示那些一旦定义后只读的特性,是静态的。而 this.state
是会随着用户互动而产生变化的特性。是动态的。
setState({xx:yyy})
,不要用this.state.xx=yyy
this.props
和this.state
可能都是异步刷新,所以,不要根据state的值去计算并定义下一个状态。好比:this.setState({ counter: this.state.counter + this.props.increment, });
实际上,setState方法还能够接收两个参数
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));//至关于return
// Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
以上两种写法是同样效果的。第一个参数是前面前一个状态,第二个参数是当下时刻的props值。
不管父或子组件都没法知道某个组件有没有state,它们也不关心这个组件被定义为一个函数或是一个类。
这就是为何state一般只在组件内部封装和调用。除了组件自身,外部组件是没法访问到该组件的state对象的。
组件能够选择经过其state来做为props:好比本章案例中的toggleClick函数。在组件嵌套的时候,你也能够把父组件的state传给子组件——经过设置字组件props。
重点来了:这一般被称作“自上而下”或“单向”数据流的行为方式了。 任何收到其它组件(好比组件A)state影响的组件(好比组件B),A一定是B的父级组件。子组件不可能经过自身的state影响上层的父级组件。
假想组件嵌套是一条河流,若是说props
是河床,那么每一个子组件的state就是各个小支流的源头。水不可能往高处流。
好比说本章例子,我建立一个新组件,包括了三个:
ReactDOM.render( <div> <ToggleState/> <ToggleState/> <ToggleState/> </div>, document.getElementById('example') );
在这个例子中,三个子组件彼此都是孤立的,本身拥有各自的state,互不影响。
一般,几个组件须要反映相同的数据变化。 最好的办法是几个子组件共享它们共同父组件的state。
好比说
在本节中,咱们将建立一个计算器来计算温度的水是否会煮在一个给定的温度。
咱们将从一个组件称为BoilingVerdict开始。 它接受摄氏温度做为支撑,并打印是否足以煮水:
react的元素处理事件方法很是相似于DOM元素的行间处理事件方法。
如今咱们知道在行间加javascript处理函数是很是不规范的。可是react的加事件处理函数并非真正的行间js。在语法和函数使用上能够看出本质差别:
<p onclick="toggleClick()"><!--行间javascript-->
在react是这样:
<p ref="para" onClick={this.toggleClick}>//这是react的方式
toggleClick:function(event){ event.preventDefault(); }
这里用到了参数event
在这里,event是一个合成的事件。它由React根据W3C规范定义,因此你没必要担忧跨浏览器兼容性。 看到SyntheticEvent参考指南了解更多信息。
使用React时你一般不须要调用addEventListener侦听器。 相反,只须要提供一个侦听器时最初渲染的元素。
好比我有一个按钮组。当点击一个按钮想得到该按钮的响应,获取方法就是event.target
必须留意JSX回调的内涵。 在JavaScript中,对象方法不受限制。在本章案例中调用toggleClick方法时,若是你不给toggleClick绑定this
,就将其传递给onClick,获得的将是undefined
。
这是一个JavaScript函数的基本原理之一。
表单是用户和网页实现动态交互的最直接实例。用户在表单填入的内容,属于用户跟组件的互动,因为虚拟DOM的特性,因此不能用 this.props
。
在HTML表单元素,如<input>
、<textarea>
,和<select>
一般根据用户输入状况而更新。 在react中,可变状态一般保存在组件的state里面,想要实时更新,只有用setState()
方法。
var Input=React.createClass({ getInitialState:function(){ return { value:'文本框内容随输入变化而变化噢' }; },//定义设置value的初始状态为hello change:function(event){//定义输入框value改变的回调函数 this.setState({ value:event.target.value//注意事件对象 }); },//触发事件后,vlue随着用户输入的value而变化。 render:function(){ var value=this.state.value; return ( <div> <input type="text" value={value} onChange={this.change}/> <p>{value}</p> </div> ); } }); ReactDOM.render( <Input/>, document.getElementById('example') )
上面代码中,文本输入框的值,不能用 this.props.value
读取,而要定义一个 onChange
事件的回调函数,经过 event.target.value
读取用户输入的值。textarea
元素、select
元素、radio
元素都属于这种状况,更多介绍请参考官方文档。
先看一个案例:实现一个下拉菜单(select-dropdown)。要求html渲染出如下信息:
<form> <label> 你喜欢: <select> <option value="男人">男人</option> <option value="女人">女人</option> <option selected value="都喜欢">都喜欢</option><!--被选中状态--> <option value="都不喜欢">都不喜欢</option> </select> </label> <input type="submit" value="提交!"/> </form>
点击提交时,弹出对应的信息。
分析:有一点须要注意:初始状态是“都喜欢”被选中。你不能直接在option里面加selected
属性。在JSX语法中。定义下拉菜单的选中状态是<selecte>
元素的value值(对应option的内容)。
var App=React.createClass({ getInitialState:function(){ return ({ list:{ "v1":"男人", "v2":"女人", "v3":"都喜欢", "v4":"都不喜欢" }, value:"都喜欢" }) }, submit:function(e){ var info='Yooooo,原来你喜欢'+this.state.value+'呀!' alert(info); e.preventDefault();//jQuery阻止冒泡 }, change:function(e){ console.log(e) this.setState({ value:e.target.value }) }, render:function(){ var list=[]; for(var i=1;i<=Object.getOwnPropertyNames(this.state.list).length;i++){ var listInfo=this.state.list["v"+i]; list.push( <option key={i.toString()} value={listInfo}>{listInfo}</option> ); } console.log(list) return ( <form> <label> 你喜欢: <select value={this.state.value} onChange={this.change}>{list}</select> </label> <input type="submit" value="提交" onClick={this.submit}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
组件的生命周期分红三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被从新渲染
- Unmounting:已移出真实 DOM
React 为每一个状态都提供了两种处理函数,will
函数在进入状态以前调用,did
函数在进入状态以后调用,三种状态共计五种处理函数。
- componentWillMount():组件插入前执行
- componentDidMount():组件插入后执行(重要)
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState):组件被移除后执行
- componentWillUnmount():组件被移除前执行
此外,React 还提供两种特殊状态的处理函数。
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否从新渲染时调用
这些方法的详细说明,能够参考官方文档。
接下来这个demo
var Hello=React.createClass({ getInitialState:function(){ return { opacity:1.0 }; }, componentDidMount:function(){//组件插入后执行! this.timer=setInterval(function(){ var opacity=this.state.opacity; opacity-=0.05; if(opacity<0.1){ opacity=1.0; } this.setState({ opacity:opacity }); }.bind(this),100);//定时器必须绑定this,不然出错 }, render:function(){ return ( <div style={{opacity:this.state.opacity}}> Hello {this.props.name} </div> ) } }); ReactDOM.render( <Hello name="World" />, document.getElementById('example') )
上面代码在hello
组件加载之后,经过 componentDidMount
方法设置一个定时器,每隔100毫秒,就从新设置组件的透明度,从而引起从新渲染。
另外,组件的style
属性的设置方式也值得注意,不能写成
style="opacity:{this.state.opacity};"
而要写成
style={{opacity: this.state.opacity}}
这是由于 React 组件样式是一个对象,因此第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。
组件的数据来源,一般是经过 Ajax 请求从服务器获取,可使用 componentDidMount
方法设置 Ajax 请求,等到请求成功,再用 this.setState
方法从新渲染 UI)。
为了获取数据并把它打到页面上其实思路和上面例子差很少。componentDidMount()
方法执行插入后渲染。
为了简化操做,咱们引入jquery文件到页面中(React自己没有任何依赖)。同时,本身作一个json文件,放到demo根目录下,在本地服务器环境下运行网页。
[ { "owner":{ "login":"Dangjingtao" }, "url":"http://www.baidu.com" } ]
接下来使用getJSON方法获取这个json数据。彻底没有用新的其它react方法:
var Info=React.createClass({ getInitialState:function(){ return { userName:'', lastGistUrl:'' }; }, componentDidMount:function(){ $.getJSON(this.props.source,function(data){ console.log(data) var lastVisit=data[0]; if(this.isMounted()){ this.setState({ userName:lastVisit.owner.login, lastVisitUrl:lastVisit.url }); } }.bind(this));//注意,涉及到的方法都绑定! }, render:function(){ return ( <div> {this.state.userName} is Last visit is <a href={this.state.lastVisitUrl}>here</a> </div> ); } }); ReactDOM.render( <Info source="json.json" />, document.getElementById('example') );
here连接向百度。
咱们甚至能够把一个Promise对象传入组件。
var RepoList = React.createClass({ getInitialState: function() { return { loading: true, error: null, data: null}; }, componentDidMount() { this.props.promise.then( value => this.setState({loading: false, data: value}), error => this.setState({loading: false, error: error})); }, render: function() { if (this.state.loading) { return <span>Loading...</span>; } else if (this.state.error !== null) { return <span>Error: {this.state.error.message}</span>; } else { var repos = this.state.data.items; var repoList = repos.map(function (repo) { return ( <li> <a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description} </li> ); }); return ( <main> <h1>Most Popular JavaScript Projects in Github</h1> <ol>{repoList}</ol> </main> ); } } });
上面代码从Github的API抓取数据,而后将Promise对象做为属性,传给RepoList
组件。
若是Promise对象正在抓取数据(pending状态),组件显示"正在加载";若是Promise对象报错(rejected状态),组件显示报错信息;若是Promise对象抓取数据成功(fulfilled状态),组件显示获取的数据。
思路:
var Time=React.createClass({ getInitialState:function(){ return { now:new Date().toLocaleTimeString() } }, componentDidMount:function(){//组件插入后执行! console.log(this) this.timer=setInterval(function(){ this.setState({ now:new Date().toLocaleTimeString() }); }.bind(this),1000);//定时器必须绑定this,不然出错 }, componentDidUnmount:function() { console.log('组件已被移除!') //clearInterval(this.timer); }, render:function(){ return ( <div> <h1>Hello, world!</h1> <h2>如今是: {this.state.now}.</h2> </div> ) } }); ReactDOM.render( <Time/>, document.getElementById('wrap') );
让咱们快速回顾一下发生了什么:
1)当<Time/>
传递给ReactDOM.render(),组件的调用构造函数。 因为时钟须要显示当前时间,它须要初始化。状态对象包括当前时间。 在插入文档以后,咱们会更新这个状态。
2)接着React调用render()方法。 把初始状态反映在屏幕上。
3)插入到真实DOM后,调用componentDidMount()。 它在插入后每秒执行一个函数,而每次执行都会从新渲染,从新插入到页面中。
4)若是组件被移除,将激活componentDidUnmount()
实际上这是数据双向绑定的例子
实现基本功能:两个输入框,在第一个摄氏温标输入框输入数字。第二个输入框自动绑定计算后的华氏温标数据。反之亦然。
另外,根据100度沸腾的原则。断定当前温度是否沸腾。
思路:输入框要注意这么一个事实:若是不进行双向绑定,会致使输出失败
var Judge=React.createClass({ getInitialState:function(){ return ({ info:'', }) }, render:function(){ console.log(this.state); //var info=''; var info=''; if(this.props.judge>=100){ info="水开了" }else{ info="水没开" }; return ( <span>{info}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ( { valueC:'', valueF:'' } ) }, change:function(e){ this.setState({ valueC:e.target.value, valueF:this.toF(e.target.value) }) }, toC:function(fahrenheit) { return (fahrenheit - 32) * 5 / 9; }, toF:function(celsius) { return (celsius * 9 / 5) + 32; }, render:function(){ return ( <form> 摄氏温度:<input type="text" onChange={this.change} value={this.state.valueC} /><br/> 华氏温度:<input type="text" onChange={this.change} value={this.state.valueF} /><br/> <Judge judge={this.state.valueC}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') )
曾经考虑过给子组件的判断函数设置状态,若是你在render函数内部(非“行间”)设置状态,会致使死循环,报错信息建议把状态设置到生命周期相关的函数中,可是这样作很是很是之卡。所以我摒弃了这个作法。
模拟一个表单,完成实时输入校验。
实际上就是设置一个状态。组件会监听你输入的文字内容,触发规则以后,改变状态,再根据这个状态修改其它监听的内容。
规则显示模块用一个子组件Judge来实现。
var Judge=React.createClass({//子组件 judge:function(type,propsValue){ var content=''; if(type=="email"){//若是type属性为email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(this.props.value==''){ content=""; }else if(reEmail.test(propsValue)){ content="输入正确!"; }else{ content="请输入正确的邮箱名!"; } } return content; }, setColor:function(ref,contentType){ //根据内容判断颜色 if(ref){//在首次加载渲染时,this.refs.main为undefined if(contentType=="输入正确!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'' }); }, change:function(e){ this.setState({ emailValue:e.target.value }); }, render:function(){ return ( <form> 邮箱:<input type="text" onChange={this.change} value={this.state.emailValue} /><br/> <Judge type="email" value={this.state.emailValue}/><br/> </form> ) } });
基本效果:
到目前为止已经实现了对Judge组件的初步封装
到目前为止,组件实现看起来都很简单。
根据一样的原理,再实现密码校验
var Judge=React.createClass({//子组件 judge:function(type,propsValue){ var content=''; if(type=="email"){//若是type属性为email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(propsValue==''){ content=""; }else if(reEmail.test(propsValue)){ content="输入正确!"; }else{ content="请输入正确的邮箱名!"; } }else if(type=="password"){//若是是password var rePassword=/^[a-zA-Z0-9]{6,10}$/; if(propsValue==''){ content=''; }else if(rePassword.test(propsValue)){ content="输入正确!"; }else{ content="密码必须包括6-10位英文字母和数字!"; } } return content; }, setColor:function(ref,contentType){ //根据内容判断颜色 if(ref){//在首次加载渲染时,this.refs.main为undefined if(contentType=="输入正确!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type,this.props.value); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'', passwordValue:'' }); }, changeEmail:function(e){ this.setState({ emailValue:e.target.value, }); }, changePassword:function(e){ this.setState({ passwordValue:e.target.value, }); },//此函数不太能传参,不得很少设一个 render:function(){ return ( <form> 邮箱:<input type="text" onChange={this.changeEmail} value={this.state.emailValue} /><br/> <Judge type="email" value={this.state.emailValue}/><br/> 密码:<input type="text" onChange={this.changePassword} value={this.state.passwordValue} /><br/> <Judge type="password" value={this.state.passwordValue}/><br/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
效果能够本身试试。
没什么说的。
实现提交按钮,把行为指向百度。若是有一个不符合规范,就不能经过。
由于Judge组件不能很方便把状态返回到上层,我以为这是该表单验证架构的最大短板。
可是父级组件能够读取下层的信息,能够给Judge组件填上本身的ref:,那么父组件就能够经过两个this.refs.xxx.refs.xxx访问到子组件返回的html结构内容。有了内容,就能够作判断了。
var Judge=React.createClass({//子组件 getInitialState:function(){ return { check:false } }, judge:function(type){ var content=''; if(type=="email"){//若是type属性为email var reEmail=/^\w+@[a-z0-9]+\.[a-z]{2,4}$/; if(this.props.value==''){ content=""; }else if(reEmail.test(this.props.value)){ content="输入正确!"; }else{ content="请输入正确的邮箱名!"; } }else if(type=="password"){//若是是password var rePassword=/^[a-zA-Z0-9]{6,10}$/; if(this.props.value==''){ content=''; }else if(rePassword.test(this.props.value)){ content="输入正确!"; }else{ content="密码必须包括6-10位英文字母和数字!"; } } return content; }, setColor:function(ref,contentType){ //根据内容判断颜色 if(ref){//在首次加载渲染时,this.refs.main为undefined if(contentType=="输入正确!"){ ref.style.color="green"; }else{ ref.style.color="red"; } } }, render:function(){ var content=this.judge(this.props.type); this.setColor(this.refs[this.props.type],content); return ( <span ref={this.props.type}>{content}</span> ) } }); var App=React.createClass({ getInitialState:function(){ return ({ emailValue:'', passwordValue:'' }); }, changeEmail:function(e){ this.setState({ emailValue:e.target.value, }); }, changePassword:function(e){ this.setState({ passwordValue:e.target.value }); },//此函数不太能传参,不得很少设一个 reset:function(){//重置实现 this.setState({ emailValue:'', passwordValue:'' }) }, submit:function(e){ // console.log(this.refs.email.refs.email.innerText); // console.log(this.refs.password.refs.password.innerText); var checkEmail=this.refs.email.refs.email.innerText; var checkPassword=this.refs.password.refs.password.innerText; if(checkEmail=="输入正确!"&&checkPassword=="输入正确!"){ alert('注册成功!'); }else{ alert('请检查你填写的信息!'); e.preventDefault(); } }, render:function(){ return ( <form method="post" action="http://www.baidu.com"> 邮箱:<input type="text" onChange={this.changeEmail} value={this.state.emailValue} /><br/> <Judge ref="email" type="email" value={this.state.emailValue}/><br/> 密码:<input type="text" onChange={this.changePassword} value={this.state.passwordValue} /><br/> <Judge ref="password" type="password" value={this.state.passwordValue}/><br/> <input type="button" value="重置" onClick={this.reset}/> <input type="submit" value="注册" onClick={this.submit}/> </form> ) } }); ReactDOM.render( <App/>, document.getElementById('example') );
选项卡怕是每一个网页设计者作的第一个组件。而第一次老是看遍原理却无从下手。在此就用前面的知识作一个选项卡吧。
一个选项卡,基本结构就是两个ul-li点击来回切换。因此主要的结构应该是:
<ul class="btns"> <li><a class="active" href="javascript:;">1</a></li> <li><a href="javascript:;">2</a></li> <li><a href="javascript:;">3</a></li> <li><a href="javascript:;">4</a></li> </ul> <ul class="imgs"> <li class="active"><img src="images/1.jpg"/></li> <li><img src="images/2.jpg"/></li> <li><img src="images/3.jpg"/></li> <li><img src="images/4.jpg"/></li> </ul> </div>
css
/*css-reset*/ *{ margin:0; padding: 0; } ul li{ list-style: none; } a{ text-decoration: none; } /******************/ .tabs{ width: 400px; margin: 200px auto; } .btns a{ display: block; width: 30px; float: left; line-height: 30px; text-align: center; } .btns li{ float: left; } .btns .active{ background: #ccc; } .imgs li{ display: none; } .imgs .active{ display: block; }
基本效果
接下来就经过react的渲染来实现功能。
事实上我以为这个最难的问题。
首先封装按钮ul组件和图片库ul组件,而后把它加到div#tabs里面去。考虑用作两个数组。
那么基本初始化样式就有了。在组件类的render函数下用循环自动生成两个数组——而后再把此数组插入到对应结构的html中。
render函数以下:
render:function(){ var numArr=[]; var imgArr=[]; for(var i=0;i<this.state.num;i++){ if(i===0){ numArr.push( <li key={i+1}><a className="active" href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li className="active" key={i+1}><img src="images/1.jpg" /></li> ); }else{ var str="images/"+(i+1).toString()+".jpg" numArr.push( <li key={i+1}><a href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li key={i+1}><img src={str} /></li> ); } } return ( <div className="tabs"> <ul className="btns"> {numArr} </ul> <ul className="imgs"> {imgArr} </ul> </div> ) },
主要思想是:设置一个名字为num的state,接收来自服务器的数据(传进来图片的张数)。而后根据这个num来设置数组须要哪些元素。
在上面的结构生成中,有个点击触发的change方法。里面的dom操做所有依赖于点击事件的this。实际上点击发生的对象来自该函数的第一个参数event.target。
change:function(event){ var $all=$(event.target).parent().siblings().children(); $all.removeClass('active'); $(event.target).addClass('active'); var index=$(event.target).parent().index();//获取索引值 var $allImgList=$(event.target).parent().parent().next().children(); $allImgList.hide(); $allImgList.eq(index).fadeIn(100); },
那么这个函数就没问题了,跟jquery选显卡的代码差很少。
咱们首先写一个json.json文件到根目录,来模拟获取的服务器数据
[ { "num":"4" } ]
设置初始状态:
getInitialState:function(){ return { num:0//来自服务器 } },
而后在虚拟dom插入到页面以前就拿到数据——提示用componentWillMount。这里是图片所展示的张数。
在此我定义一个getListUrl属性,
componentWillMount:function(){ $.getJSON(this.props.getListUrl,function(data){ //console.log(this.state); this.setState({ num:data[0]["num"]//设置状态值为获取到的数据 }) }.bind(this)) },
所有代码以下
var App=React.createClass({ getInitialState:function(){ return { num:0//来自服务器 } }, componentWillMount:function(){ $.getJSON(this.props.getListUrl,function(data){ //console.log(this.state); this.setState({ num:data[0]["num"] }) }.bind(this)) }, change:function(event){ var $all=$(event.target).parent().siblings().children(); $all.removeClass('active'); $(event.target).addClass('active'); var index=$(event.target).parent().index(); var $allImgList=$(event.target).parent().parent().next().children(); $allImgList.hide(); $allImgList.eq(index).fadeIn(100); }, render:function(){ var numArr=[]; var imgArr=[]; for(var i=0;i<this.state.num;i++){// if(i===0){ numArr.push( <li key={i+1}><a className="active" href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li className="active" key={i+1}><img src="images/1.jpg" /></li> ); }else{ var str="images/"+(i+1).toString()+".jpg" numArr.push( <li key={i+1}><a href="javascript:;" onClick={this.change}>{i+1}</a></li> ); imgArr.push( <li key={i+1}><img src={str} /></li> ); } } return ( <div className="tabs"> <ul className="btns"> {numArr} </ul> <ul className="imgs"> {imgArr} </ul> </div> ) }, }); ReactDOM.render(<App getListUrl="json.json"/>,document.getElementById('wrap'));
效果