跟我一起学 React 😄,只为了缓解大家学习React的痛苦。
懂得也不多,所以有神马我理解错误的地方,请指正。
React 各大BAT都在用的框架,学到就是赚到。
这是我的 Learn 笔记,同样也是一份简明教程。
列一下资料:
React英文版指南
# 先来准备一个基本页面
已经替换为国内的 CDN。
react.js -> React主要核心
react-dom.js -> React DOM 操作
browser.min.js -> 将 babel 转换成浏览器可用的 ES5
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="http://cdn.bootcss.com/react/0.14.1/react.js"></script>
<script src="http://cdn.bootcss.com/react/0.14.1/react-dom.js"></script>
<script src="http://cdn.bootcss.com/babel-core/5.8.32/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
// ** Your code goes here! **
</script>
</body>
</html>
# 写一个简单时间(它看起来想这样)
props -> properties 属性们
render -> 渲染
ReactDom.render(实例化的组件,要渲染到得对象)
在HelloWorld组件中,传入了一个data属性,对应的是一个Date对象。
在组件中,通过 this.props.date 拿到Date对象,调用它的toTimeString方法。
而且本例中我们是用了一个定时器,每500ms我们实例化渲染一次组件,更新时间的值。
<HelloWorld date={new Date()}/> 这是jsx语法,有点类似于HTML5自定义语义标签吧。
可以这么理解,每一个标签就是一个组件,一个对象,因为我们是用的React.createClass方法去创建的,从方法名上就可以看出。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="http://cdn.bootcss.com/react/0.14.1/react.js"></script>
<script src="http://cdn.bootcss.com/react/0.14.1/react-dom.js"></script>
<script src="http://cdn.bootcss.com/babel-core/5.8.32/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var HelloWorld = React.createClass({
render: function() {
return (
<p>
你好, <input type="text" placeholder="输入你的名字"/>! <br/><br/>
现在的时间是:{this.props.date.toTimeString()}
</p>
);
}
});
setInterval(function() {
ReactDOM.render(
<HelloWorld date={new Date()}/>,
document.getElementById('example')
);
}, 500);
</script>
</body>
</html>
# 让我们细化知识点吧(JSX语法)
var myDivElement = <div className="foo" />;
// className 就代表 class属性,因为怕属性冲突,所有就另外给了一个名字。
ReactDOM.render(
myDivElement,
document.getElementById('example')
);
// 输入:
React.createElement(Profile, null, "click")
// 转义输出为:
<Profile>click</Profile>
var Nav, Profile;
// 输入 (JSX):
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// 转义输出为 (JS):
var app = React.createElement(
Nav,
{color:"blue"},
React.createElement(Profile, null, "click")
);
# 组件的命名空间(JSX语法)
// App组件就把这几个组件整合,打包起来了。
var Form = MyFormComponent;
var FormRow = Form.Row;
var FormLabel = Form.Label;
var FormInput = Form.Input;
var App = (
<Form>
<FormRow>
<FormLabel />
<FormInput />
</FormRow>
</Form>
);
//用子组件的方式去整合打包
var MyFormComponent = React.createClass({ ... });
MyFormComponent.Row = React.createClass({ ... });
MyFormComponent.Label = React.createClass({ ... });
MyFormComponent.Input = React.createClass({ ... });
//使用 React.createElement 的第三个参数
var App = (
React.createElement(Form, null,
React.createElement(Form.Row, null,
React.createElement(Form.Label, null),
React.createElement(Form.Input, null)
)
)
);
# 布尔属性、表达式与注释(JSX语法)
// 禁用样式的按钮
<input type="button" disabled />;
<input type="button" disabled={true} />;
// 正常使用的按钮
<input type="button" />;
<input type="button" disabled={false} />;
// 输入 (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// 三元运算符,window.isLoggedIn 存在输出<Nav />组件,否则输出<Login/>组件
// 输出 (JS):
var content = React.createElement(
Container,
null,
window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);
var content = (
<Nav>
{/* 子组件注释,加上 {} 花括号 */}
<Person
/* 组件
属性
注释
*/
name={window.isLoggedIn ? window.name : ''} // end of line comment
/>
</Nav>
);
# 属性传入组件的多种方式(JSX语法)
//变量放在{}中
var component = <Component foo={x} bar={y} />;
var component = <Component />;
component.props.foo = x; // 不推荐,最丑的做法
component.props.bar = y; // 不推荐,颜值低得人可以这么干
//传入对象的方式传入属性
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
//注意,后面会覆盖前面的
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 输出为'override'
# JSX 陷阱(JSX做了一些处理防止XSS攻击)
// 会显示 “First · Second”
<div>{'First · Second'}</div>
// 它会显示 "First · Second"
<div>First · Second</div>
// 正确做法,帅的人都这么干
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
// 同时你还可以这样玩,加上[],以数组的形式。
<div>{['First ', <span>·</span>, ' Second']}</div>
//但是有的适合,我的项目中就需要这样干,就要原来的。
//给dangerouslySetInnerHTML传入一个对象,其中有一个__html属性,其中写了你的文本。
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
//假如你想加上自定义属性,必须加上data-前缀
//以aria-开头的属性页可以被渲染出来
<div data-custom-attribute="foo" />
<div aria-hidden={true} />
# 组件生命周期(别纠结这是干什么,纠结你就输了,先看一看文字,不用去弄懂他们,跳过都行,细节后面在讲解)
初始化阶段
getDefaultPropos:只调用一次,实力之间共享引用
getInitialState:初始化每个实例特有的状态
componentWillMount:render之前最后一次修改状态的机会
render:只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
componentDidMount:成功render并渲染完成真实DOM后触发,可以修改DOM
运行中阶段
componentWillReceiveProps:父组件修改属性触发,可以修改新属性,修改状态
shouldComponentUpdate:返回false会阻止render调用
componentWillUpeate:不能修改属性和状态
render:只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
componentDidUpdate:可以修改DOM
销毁阶段:
componentWillUnMount:在删除组件之前进行清理操作,比如计时器和事件监听器。
附上一些我当初的截图笔记,可以看出他们运行顺序(当初用的是JSX转换库):
初始化阶段
<script type="text/jsx">
$(function(){
var count = 0;
var style={
color:"red",
border:"1px #000 solid"
};
var HelloWorld = React.createClass({
getDefaultProps:function(){
console.log(1);
return {name:"Yugo"};
},
getInitialState:function(){
console.log(2);
return {
myCount: count = count+1 ,
ready: "false"
};
},
componentWillMount:function(){
console.log(3);
// 修改状态
this.setState({ready:"true"});
},
render:function(){
console.log(4);
return <p ref="childp">Hello,{this.props.name?this.props.name:"World"}<br/> {this.state.ready} {this.state.myCount}</p>;
},
componentDidMount:function(){
console.log(5);
//改变DOM
$(React.findDOMNode(this)).append("666666");
},
})
React.render(<div style={style}><HelloWorld></HelloWorld></div>,document.body)
});
</script>
运行中阶段
var style={
color:"red",
border:"1px #000 solid"
};
var HelloWorld = React.createClass({
componentWillReceiveProps:function(){console.log(1);},
shouldComponentUpdate:function(){console.log(2);return true;},
componentWillUpdate:function(){console.log(3);},
render:function(){
console.log(4);
return <p>Hello,{this.props.name?this.props.name:"World"}</p>;
},
componentDidUpdate:function(){console.log(5);},
});
var HelloUniverse = React.createClass({
getInitialState:function(){
return {name:''};
},
handleChange:function(event){
this.setState({name: event.target.value})
},
render:function(){
return <div>
<HelloWorld name={this.state.name}></HelloWorld>
<br/>
<input type="text" onChange={this.handleChange} />
</div>
},
});
React.render(<div style={style}><HelloUniverse></HelloUniverse></div>,document.body)
销毁阶段
var style={
color:"red",
border:"1px #000 solid"
};
var HelloWorld = React.createClass({
render:function(){
console.log(4);
return <p>Hello,{this.props.name?this.props.name:"World"}</p>;
},
componentWillUnmount:function(){
console.log("Delete`````````!");
},
});
var HelloUniverse = React.createClass({
getInitialState:function(){
return {name:''};
},
handleChange:function(event){
if(event.target.value == 123){
React.unmountComponentAtNode(document.body);
return;
}
this.setState({name: event.target.value})
},
render:function(){
return <div>
<HelloWorld name={this.state.name}></HelloWorld>
<br/>
<input type="text" onChange={this.handleChange} />
</div>
},
});
React.render(<div style={style}><HelloUniverse></HelloUniverse></div>,document.body)
# 来一个简单小栗子(根据状态去做不同的行为)
state -> 状态
React.createClass方法,我们是传入的一个对象。
而这个对象的生命周期分为三个,初始化、运行中、销毁阶段。
再介个栗子中,getInitialState是属于初始化阶段的。
他返回了一个对象,这个对象中包含组件的状态。
我们可以通过this.state去获得这个对象。
通过setState(data, callback)方法去设置状态,同样是传入对象。
onClick就是鼠标单击事件喽,鼠标单击执行自定义函数handleClick。
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
什么时候使用状态(百度翻译了一下原文)?
你的大部分组件应该只需从props中取一些数据并渲染它。然而,
有时你需要对用户输入、服务器请求或时间的推移作出反应。为此您使用state状态。
尽可能保持尽可能多的组件成为可能的state状态。
通过这样做,您将分离到它最合乎逻辑的地方,并尽量减少冗余。
一个常见的模式是创建多个无状态的组件,只是提供props数据,
并有一个有state状态的组件上面,通过其state状态通过子组件的层次。
有状态组件封装了所有的交互逻辑,而无状态的组件专注呈现数据。
建设一个有状态的组件时,考虑其状态的最小可能性,
this.state应该只包含需要代表你的UI状态的最小数据量,不要包含计算数据、反应元件(基于基本的道具和状态建立他们)
用我自己的话说吧(2个小栗子):
我能吐槽这个最小可能性,和最小数据量吗?
当需要改变组件的行为的时候,我们可能要去判断状态。
🌰一:有病状态就该吃药,没病也别没事瞎嗑药,容易出事,但是有病的还是少数人。
🌰二:师傅有五个徒弟,分为大师兄徒弟(武力值最高),和其他徒弟俩类。
师傅出去云游,估计某处青楼里创作惊世神功,大师兄通常是代理师傅。根据门派的状态去给其他师弟师妹派任务。
大师兄:二师弟,厨房木有米了,你负责下山去买米。
只见大师兄拿出笔和纸,写了一大页满的。考虑到师傅10年后回来的概率(20%),与各位师兄弟的饭量,刚刚好200万。
买200万斤吧!,多一斤也不要,我就要这个最小数据量,不包含其他算法处理,不要因为价格,带的钱不够,就买少了,就要实实在在的这么多,带上神兽麒麟,驼回来,考虑到200年之后没米吃,对,就这么干。
还有你不要有反应元件,我知道你比较冲动,不能用为老板给的价格不合理,你就砍了他,咱门派的人不乱杀人,摊上事了,别冲动,好好解决。跟他谈个好几年的价格也是可以的。
二师弟:( ⊙ o ⊙ )啊!!!,师兄高瞻远就,我这就去办。
(尼玛,考虑最小可能性也没必要这么干吧,200年啊!!!,还最小数据量,尼玛怎么算得啊!拉格朗日一下就出来了?)
大师兄:三师妹,你就负责服侍大师兄我吧!来,先捶捶腿。
三师妹:~~~~(>_<)~~~~
大师兄:四师弟,马桶堵了,你去掏马桶。
四师弟:哦!(逼了狗了~,还不如买米去。)
大师兄:五师弟,念你年纪小,上山去砍柴,多锻炼才能长得结实。
五师弟:.....(默默背上柴刀走了)
故事就是这样,这个故事告诉我们,要么做大师兄,要么回家种田。
(管事的,管状态的越少越好,一个最好,这样才不会乱。)
# 看看大师兄和师弟们是如何协作的。
介个是一个评论中的头像组件
// Avatar组件包含2个子组件 ProfilePic、ProfileLink。
// ProfilePic:个人头像
// ProfileLink:个人主页链接
// Avatar组件传入了username属性props
// 通过 this.props.username 再传递给子组件
var Avatar = React.createClass({
render: function() {
return (
<div>
<ProfilePic username={this.props.username} />
<ProfileLink username={this.props.username} />
</div>
);
}
});
var ProfilePic = React.createClass({
render: function() {
return (
<img src={'https://graph.facebook.com/' + this.props.username + '/picture'} />
);
}
});
var ProfileLink = React.createClass({
render: function() {
return (
<a href={'https://www.facebook.com/' + this.props.username}>
{this.props.username}
</a>
);
}
});
ReactDOM.render(
<Avatar username="pwh" />,
document.getElementById('example')
);
就如同大师兄知道没米,叫二师弟去买米。
师傅留下麒麟,没带走,然后又交给二师弟去驮米一样。
(不知道师傅回来会不会一巴掌打屎他)
# 关于子组件
<Parent><Child /></Parent>
//通过 this.props.children 可以操作子组件
// 第一次输出
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// 第二次更新输出
<Card>
<p>Paragraph 2</p>
</Card>
//Paragraph 1 将会被移除掉
今天是第二天,时间是1点半。
惆怅,今天11点钟起来,帮管朋友的Server2003服务器(老的不像样)挂掉了。
最近逃课也经常被点到,我也是醉了。
言归正传,继续学习。
# 动态组件
array.map(value)函数遍历一个数组。
result.id 是起了标识作用
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>
);
}
标识id应该在组件上,
// 错误!丑的人都这样干,反正我是不这样干。
var ListItemWrapper = React.createClass({
render: function() {
return <li key={this.props.data.id}>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper data={result}/>;
})}
</ul>
);
}
});
// 正确 :)
var ListItemWrapper = React.createClass({
render: function() {
return <li>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper key={result.id} data={result}/>;
})}
</ul>
);
}
});
# PropTypes,类型约束
为了性能考虑,只在开发环境验证 propTypes
对于我这种没有队友的人来说,感觉鸡肋,多此一举,大家不喜欢就跳过吧~我也跳了。
React.createClass({
propTypes: {
// 可以声明 prop 为指定的 JS 基本类型。默认
// 情况下,这些 prop 都是可传可不传的。
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// 所有可以被渲染的对象:数字,
// 字符串,DOM 元素或包含这些类型的数组。
optionalNode: React.PropTypes.node,
// React 元素
optionalElement: React.PropTypes.element,
// 用 JS 的 instanceof 操作符声明 prop 为类的实例。
optionalMessage: React.PropTypes.instanceOf(Message),
// 用 enum 来限制 prop 只接受指定的值。
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// 指定的多个对象类型中的一个
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// 指定类型组成的数组
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// 指定类型的属性构成的对象
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// 特定形状参数的对象
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// 以后任意类型加上 `isRequired` 来使 prop 不可空。
requiredFunc: React.PropTypes.func.isRequired,
// 不可空的任意类型
requiredAny: React.PropTypes.any.isRequired,
// 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接
// 使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error('Validation failed!');
}
}
},
});
# 把自身所有属性,传给子组件
var CheckLink = React.createClass({
render: function() {
// 这样会把 CheckList 所有的 props 复制到 <a>
return <a {...this.props}>{'√ '}{this.props.children}</a>;
}
});
ReactDOM.render(
<CheckLink href="/checked.html">
Click here!
</CheckLink>,
document.getElementById('example')
# Mixins(学过less、sass可能对这个比较熟悉)
说白了这货就是一个代码块,在哪用,就原封不动复制到哪。
不过要放在这个 mixins 对应的数组里面。
假如传入的多个mixins里面包含多个生命周期函数。
保证都会执行到得,首先按 mixin 引入顺序执行 mixin 里方法,
最后执行组件内定义的方法。
第一步,从getInitialState 得到初始化 seconds = 0
第二步,componentWillMount 中给当前对象挂了一个intervals数组。
第三步,render渲染模板到页面
第四步,componentDidMount,DOM渲染完毕后。
调用 this.setInterval(this.tick, 1000)。
第五步,this.intervals.push(setInterval.apply(null, arguments));
给intervals数组推入一个函数。里面一直就只有一个元素。
至于为什么推入数组中,是为了闭包,更好的销毁。
这个函数是系统的setInterval定时器函数。
用apply触发,第一个参数是this代表的值,第二个是参数。
setInterval是在全局window对象下面的。
而当前环境的this,是 React 组件对象,所以才用apply方法。
每一秒调用一次 this.tick,更新 seconds的状态值。
第六步,更新状态重新渲染。
第七步,1秒后继续更新状态再渲染。
在控制台中的输出顺序是: 1234563636363....
var SetIntervalMixin = {
componentWillMount: function() {
console.log(2);
this.intervals = [];
},
setInterval: function() {
console.log(5);
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
console.log(7)
this.intervals.map(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // 引用 mixin
getInitialState: function() {
console.log(1);
return {seconds: 0};
},
componentDidMount: function() {
console.log(4);
this.setInterval(this.tick, 1000); // 调用 mixin 的方法
},
tick: function() {
console.log(6);
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
console.log(3);
// console.log(this.intervals);
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
ReactDOM.render(
<TickTock />,
document.getElementById('example')
);
# 把自身部分传给子组件
利用了ES6的析构特性
var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
关于bind方法
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var getX = module.getX;
getX(); // 9, 因为在这个例子中,"this"指向全局对象
// 创建一个'this'绑定到module的函数
var boundGetX = getX.bind(module);
boundGetX(); // 81
var FancyCheckbox = React.createClass({
render: function() {
var { checked, ...other } = this.props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` 包含 { onClick: console.log } 但 checked 属性除外
return (
<div {...other} className={fancyClass} />
);
}
});
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
# 事件与表单组件
event.target -> 代表事件产生的对象(事件目标)
event.target.value -> 事件目标的value属性值
//受限组件,你要是把值直接写成Value属性。
//那你就什么都输入不了。
var FancyCheckbox = React.createClass({
render: function() {
return <input type="text" value="Hello!" />;
}
});
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
//设置初始值用 defaultValue
render: function() {
return <input type="text" defaultValue="Hello!" />;
}
// 多选
<select multiple={true} value={['B', 'C']}>
// 这样使用更为优雅,这是一个受限组件
<textarea name="description" value="This is a description." />
//正确的做吧是把它定义为状态,当值改变触发更新
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}
# React的虚拟DOM
我们可以知道React显示出来是在render渲染到页面之后。
而在之前的DOM标签全是虚拟的,所以才能让运行速度非常的快。
但是我们也可以照样操作DOM,不过要等真实DOM渲染之后。
# ref 属性
<input ref="myInput" />
var input = this.refs.myInput; // 得到实例
// this.refs['myInput'] 也可以
var inputValue = input.value; // 得到value属性值
var inputRect = input.getBoundingClientRect(); //得到React实例
ES6新特性
(ref) => this.myTextInput = ref
等于
function(ref){
return this.myTextInput = ref
}
var MyComponent = React.createClass({
handleClick: function() {
//使得实例获得焦点
this.myTextInput.focus();
},
render: function() {
// 假如ref是一个函数,那么会传一个this对象到为参数传进去
//(好像这样才解释的通)
return (
<div>
<input type="text" ref={(ref) => this.myTextInput = ref} />
<input
type="button"
value="Focus the text input"
onClick={this.handleClick}
/>
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
这几天,有点忙,老是被朋友叫去帮他们调试JSP,一调一晚上。
对此事,我只想说一句,我在北方的寒夜里,玩着灵车漂移。
突然想多看看视频,自己了解深刻一点在写。
So Sorry!朋友们
接下来会有React事件,Mixin深入(源码阅读,双向绑定),动画,尽请期待。