函数是面向过程的,函数的调用不需要主体,而方法是属于对象的,调用方法需要一个主体-即对象。
npm install -g create-react-app
创建react项目(具体看react-commend) 项目名 描述 作者等配置可以全部默认
先创建文件夹 cd到目录下 create-react-app my-app
(注意此处名字不能有大写)
然后 cd my-app
用npm start
启动
组件的state应该用来存储组件的事件处理函数随时可能会改变的数据,以达到重新渲染并保持组件的用户界面最新的目的
react事件触发,如onClick,onChange={this.fn.bind(this)}要用bind绑定事件以辨认
文末有贴士,也算是一种规范,习惯。
直接使用 BootCDN 的 React CDN 库,地址如下:
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
react.min.js - React 的核心库
react-dom.min.js - 提供与 DOM 相关的功能
babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码,这样我们就能在目前不支持 ES6 浏览器上执行 React 代码。Babel 内嵌了对 JSX 的支持。通过将 Babel 和 babel-sublime 包(package)一同使用可以让源码的语法渲染上升到一个全新的水平。
ReactDOM.render( /代码将一个 h1 标题,插入 id="example" 节点中。
<h1>Hello, world!</h1>,
document.getElementById('example')
);
JSX语法
React 使用 JSX 来替代常规的 JavaScript。
- JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
在 JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中
在 JSX 中不能使用 if else 语句,但可以使用 conditional (三元运算) 表达式来替代。
ReactDOM.render(
<div>
<h1>{i == 1 ? 'True!' : 'False'}</h1>
</div>
,
document.getElementById('example')
);
JSX 允许在模板中插入数组,数组会自动展开所有成员:
var arr = [
<h1>菜鸟教程</h1>,
<h2>学的不仅是技术,更是梦想!</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
1、在标签内部的注释需要花括号
2、在标签外的的注释不能使用花括号
ReactDOM.render(
/*注释 */
<h1>孙朝阳 {/*注释*/}</h1>,
document.getElementById('example')
);
代码中嵌套多个 HTML 标签,需要使用一个标签元素包裹它
React 组件
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello World!</h1>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
React.createClass 方法用于生成一个组件类 HelloMessage。
<HelloMessage /> 实例组件类并输出信息。
定义的 React 类名以大写字母开头,注意组件类只能包含一个顶层标签。
如果我们需要向组件传递参数,可以使用 this.props 对象:
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name}</h1>;
}
});
ReactDOM.render(
<HelloMessage name="Runoob" />,
document.getElementById('example')
);
class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。
可以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离。
组件名不一定是用单标签,也可以是双标签
<HelloMessage /> == <HelloMessage></HelloMessage>
React State(状态)
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
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 ? '喜欢' : '不喜欢';
return (
<p onClick={this.handleClick}>
你<b>{text}</b>我。点我切换状态。
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
与原生 HTML 不同,on 之后第一个字母是大写的。 onClick={this.handleClick}
React Props
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。state 来更新和修改数据。 而子组件只能通过 props 来传递数据。
可以通过 getDefaultProps() 方法为 props 设置默认值:
var HelloMessage = React.createClass({
getDefaultProps: function() {
return {
name: 'Runoob'
};
},
render: function() {
return <h1>Hello {this.props.name}</h1>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
可以在父组件中设置 state, 并通过在子组件上使用 props 将其传递到子组件上。
React 组件 API
setState(object nextState[, function callback])
合并nextState和当前state,并重新渲染组件。setState是React事件处理函数中和请求回调函数中触发UI更新的主要方法。setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,为了提升性能React会批量执行state和DOM渲染。setState()总是会触发一次组件重绘。
- 设置状态:setState
- 替换状态:replaceState
- 设置属性:setProps
- 替换属性:replaceProps
- 强制更新:forceUpdate
- 获取DOM节点:findDOMNode
- 判断组件挂载状态:isMounted
var HelloMessage = React.createClass({
getInitialState: function() {
return {value: 'Hello Runoob!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
var value = this.state.value;
return <div>
<input type="text" value={value} onChange={this.handleChange} />
<h4>{value}</h4>
</div>;
}
});
ReactDOM.render(
<HelloMessage />,
document.getElementById('example')
);
React小书
组件化可以帮助我们解决前端结构的复用性问题,整个页面可以由这样的不同的组件组合、嵌套构成。组件的显示形态和行为可以由数据状态(state)和配置参数(props)共同决定。
编译阶段你需要借助 Babel;需要 Redux 等第三方的状态管理工具来组织代码;如果你要写单页面应用那么你需要 React-router。这就是所谓的“React.js全家桶”。
一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。
JSX 在编译的时候会变成相应的 JavaScript 对象描述。
我们在编写 React.js 组件的时候,一般都需要继承 React.js 的 Component(还有别的编写组件的方式我们后续会提到)。一个组件类必须要实现一个 render 方法,这个 render 方法必须要返回一个 JSX 元素。但这里要注意的是,必须要用一个外层的 JSX 元素把所有内容包裹起来。返回并列多个 JSX 元素是不合法的,下面是错误的做法:
render () {
return (
<div>第一个</div>
<div>第二个</div>
)
}
正确写法:
render () {
return (
<div>
<div>第一个</div>
<div>第二个</div>
</div>
)
}
{} 内可以放任何 JavaScript 的代码,包括变量、表达式计算、函数执行等等。 render 会把这些代码返回的内容如实地渲染到页面上,非常的灵活。
表达式插入不仅仅可以用在标签内部,也可以用在标签的属性上。
JSX 元素其实可以像 JavaScript 对象那样自由地赋值给变量,或者作为函数参数传递、或者作为函数的返回值。
作为变量:
const isGoodWord = true
const goodWord = <strong> is good</strong>
const badWord = <span> is not good</span>
{isGoodWord ? goodWord : badWord}
// 作为函数:
renderGoodWord (goodWord, badWord) {
const isGoodWord = true
return isGoodWord ? goodWord : badWord
} // 伪代码
{this.renderGoodWord(
<strong> is good</strong>,
<span> is not good</span>
)}
自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。
class Index extends Component {
render () {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)}}
ReactDOM.render(
<Index />,
document.getElementById('root')
) //组件间的嵌套 树状结构
React.js 监听事件
只需要给需要监听事件的元素加上属性类似于 onClick、onKeyDown 这样的属性。
React.js 帮我们封装好了一系列的 on* 的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on* 就可以了。这些事件属性名都必须要用驼峰命名法。
这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。也就是说,<Header onClick={…} /> 这样的写法不会有什么效果的。
事件监听函数会被自动传入一个 event 对象,event 对象并不是浏览器提供的,而是它自己内部所构建的。
class Title extends Component {
handleClickOnTitle (e) {
console.log(e.target.innerHTML) }
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this)}>React 小书</h1>
) }
}
关于事件中的 this
React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClickOnTitle),而是直接通过函数调用 (handleClickOnTitle),所以事件监听函数内并不能通过 this 获取到实例。如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind 到当前实例上再传入给 React.js。bind 会把实例方法绑定到当前实例上,然后我们再把绑定后的函数传给 React.js 的 onClick 事件监听。
bind 不仅可以帮我们把事件监听方法中的 this 绑定到当前组件实例上;还可以帮助我们在在渲染列表元素的时候,把列表元素传入事件监听函数当中,后面会介绍。
class LikeButton extends Component {
constructor () {
super()
this.state = { isLiked: false } // 这个对象在构造函数里面初始化
}
handleClickOnLikeButton () {
this.setState({ // setState 函数,每次点击都会更新 isLiked 属性
isLiked: !this.state.isLiked })
}
}
setState 方法由父类 Component 所提供。
React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的 setState 都进行合并以后再重新渲染组件。所以并不需要担心多次进行 setState 会带来性能问题。
组件内部是通过 this.props 的方式获取到组件的参数的,如果 this.props 里面有需要的属性我们就采用相应的属性,没有的话就用默认的属性。
在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为 props 对象的键值:
class Index extends Component {
render () {
return (
<div>
<LikeButton likedText='已赞' unlikedText='赞' />
</div>
) }
}
前面的章节我们说过,JSX 的表达式插入可以在标签属性上使用。所以其实可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等:
<LikeButton wordings={{likedText: '已赞', unlikedText: '赞'}} />
JSX 的 {} 内可以嵌入任何表达式,{{}} 就是在 {} 内部用对象字面量返回一个对象而已。
static defaultProps = { //如果没有传进来,会直接使用 defaultProps 中的默认属性。
likedText: '取消',
unlikedText: '点赞'
} //defaultProps 作为点赞按钮组件的类属性,里面是对 props 中各个属性的默认配置。
state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。尽量少地用 state,尽量多地用 props。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
渲染列表数据
一般来说,在 React.js 处理列表就是用 map 来处理、渲染的。
{ users.map((user) => <User user = {user} />) }
对于用表达式套数组罗列到页面上的元素,都要为每个元素加上 key 属性,这个 key 必须是每个元素唯一的标识。一般来说,key 的值可以直接后台数据返回的 id,因为后台的 id 都是唯一的。
{ users.map((user, i) => <User key={i} user={user} />)}
在实际项目当中,如果你的数据顺序可能发生变化,标准做法是最好是后台数据返回的 id 作为列表元素的 key。
React.js生命周期
React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载(这个定义请好好记住)。
挂载阶段
React.js 会在组件的 render 之前调用 componentWillMount,在 DOM 元素塞入页面以后调用 componentDidMount。而componentWillUnmount控制了这个组件的删除过程。
-> constructor()
-> componentWillMount()
-> render()
// 然后构造 DOM 元素插入页面
-> componentDidMount()
// ...
// 即将从页面中删除
-> componentWillUnmount()
// 从页面中删除
我们一般会把组件的 state 的初始化工作放在 constructor 里面去做;
在 componentWillMount 进行组件的启动工作,例如 Ajax 数据拉取、定时器的启动;组件从页面上销毁的时候,有时候需要一些数据的清理,例如定时器的清理,就会放在 componentWillUnmount 里面去做。如下:
constructor () {
super()
this.state = {
date: new Date()
}
}
每隔 1 秒更新中的 state.date,这样页面就可以动起来了。
componentWillMount () {
this.timer = setInterval(() => {
this.setState({ date: new Date() })
}, 1000)
}
现页面上有个按钮可显示或者隐藏时钟,当时钟隐藏的时候,我们并没有清除定时器。
添加 componentWillUnmount,在组件销毁的时候清除该组件的定时器:
componentWillUnmount () {
clearInterval(this.timer)
}
更新阶段(说白了就是 setState,先了解即可)
shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
componentWillUpdate():组件开始重新渲染之前调用。
componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。
ref属性
React.js 当中提供了 ref 属性来帮助我们获取已经挂载的元素的 DOM 节点,你可以给某个 JSX 元素加上 ref属性:
componentDidMount () {
this.input.focus()
}
render () {
return (
<input ref={(input) => this.input = input} />
) }
} //我们就可以通过 this.input 获取到这个 DOM 元素。
就可以在 componentDidMount 中使用这个 DOM 元素。并且调用 this.input.focus() 的 DOM API。
但是记住一个原则:能不用 ref 就不用。不利于我们理解和维护。
其实可以给组件标签也加上 ref ,例如:
<Clock ref={(clock) => this.clock = clock} />
props.children
组件本身是一个不带任何内容的方形的容器,可以在用这组件的时候给它传入任意内容
嵌套的结构在组件内部都可以通过 props.children 获取到,这种组件编写方式在编写容器类型的组件当中非常有用。而在实际的 React.js 项目当中,我们几乎每天都需要用这种方式来编写组件。
http://huziketang.com/books/react/lesson22
{this.props.children}
React.js 就是把我们嵌套的 JSX 元素一个个都放到数组当中,然后通过 props.children 传给了Card
dangerouslySetHTML 和 style 属性
render () {
return (
<div
className='editor-wrapper'
dangerouslySetInnerHTML={{__html: this.state.content}} />
) }
需要给 dangerouslySetInnerHTML 传入一个对象,这个对象的 __html 属性值就相当于元素的 innerHTML,这样我们就可以动态渲染元素的 innerHTML 结构了。因为设置 innerHTML 可能会导致跨站脚本攻击(XSS).这个属性不必要的情况就不要使用。
style 接受一个对象,这个对象里面是这个元素的 CSS 属性键值对,原来 CSS 属性中带 - 的元素都必须要去掉 - 换成驼峰命名,如 font-size 换成 fontSize,text-align 换成 textAlign。我们用 setState 就可以修改样式。
<h1 style={{fontSize: '12px', color: this.state.color}}>React.js 小书</h1>
只要简单地 setState({color: 'blue'}) 就可以修改元素的颜色成蓝色。
React 提供的第三方库 prop-types
通过 PropTypes 给组件的参数做类型限制,这在构建大型应用程序的时候特别有用。
npm install --save prop-types
http://huziketang.com/books/react/lesson24
在文件头部引入了 PropTypes import PropTypes from 'prop-types'
并且给 Comment 组件类添加了类属性 propTypes,里面的内容的意思就是你传入的 comment 类型必须为 object(对象)。isRequired 关键字来强制组件某个参数必须传入.:
static propTypes = {
comment: PropTypes.object.isRequired
}
贴士
组件的私有方法都用 _ 开头,所有事件监听的方法都用 handle 开头。把事件监听方法传给组件的时候,属性名用 on 开头。监听(on)CommentInput 的 Submit 事件,并且交给 this 去处理(handle)。这样思路非常清晰。
<CommentInput onSubmit={this.handleSubmitComment.bind(this)} />
组件的内容编写顺序如下:
- static 开头的类属性,如 defaultProps、propTypes。
- 构造函数,constructor。
- getter/setter(还不了解的同学可以暂时忽略)。
- 组件生命周期。
- _ 开头的私有方法。
- 事件监听方法,handle*。
- render开头的方法,有时候 render() 方法里面的内容会分开到不同函数里面进行,这些函数都以 render 开头。
- render() 方法。
React Router
npm install -S react-router
使用时,路由器Router就是React的一个组件。
import { Router } from 'react-router';
render(<Router/>, document.getElementById('app'));
Router组件本身只是一个容器,真正的路由要通过Route组件定义。
import { Router, Route, hashHistory } from 'react-router';
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'));
用户访问根路由/(比如http://www.example.com/),组件APP就会加载到document.getElementById('app')。Router组件有一个参数history,它的值hashHistory表示,路由的切换由URL的hash变化决定,即URL的#部分发生变化。举例来说,用户访问http://www.example.com/,实际会看到的是http://www.example.com/#/。
Route组件定义了URL路径与组件的对应关系。你可以同时使用多个Route组件。
<Router history={hashHistory}>
<Route path="/" component={App}/>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
上面代码中,用户访问/repos(比如http://localhost:8080/#/repos)时,加载Repos组件;访问/about(http://localhost:8080/#/about)时,加载About组件。
嵌套路由
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
上面代码,用户访问/repos时,会先加载App组件,然后在它的内部再加载Repos组件。
<App>
<Repos/>
</App>
this.props.children属性就是子组件。
path 属性
Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
当用户访问 /inbox/messages/:id 时,会加载下面的组件。
<Inbox>
<Message/>
</Inbox>
如果省略外层Route的path参数,写成下面的样子。
<Route component={Inbox}>
<Route path="inbox/messages/:id" component={Message} />
</Route>
现在用户访问 /inbox/messages/:id 时,组件加载还是原来的样子。
path属性可以使用通配符。
<Route path="/hello/:name">
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/hello(/:name)">
// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/files/*.*">
// 匹配 /files/hello.jpg
// 匹配 /files/hello.html
<Route path="/files/*">
// 匹配 /files/
// 匹配 /files/a
// 匹配 /files/a/b
<Route path="/**/*.jpg">
// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg
通配符的规则如下。
(1):paramName
:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。
(2)()
()表示URL的这个部分是可选的。
(3)*
*匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式。
(4) **
** 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式。
路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了。
设置路径参数时,需要特别小心这一点。
<Router>
<Route path="/:userName/:id" component={UserPage}/>
<Route path="/about/me" component={About}/>
</Router>
IndexRoute 组件
显式指定Home是根路由的子组件,即指定默认情况下加载的子组件。你可以把IndexRoute想象成某个路径的index.html。
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
用户访问 / 的时候,加载的组件结构如下。
<App>
<Home/>
</App>
App只包含下级组件的共有元素,本身的展示内容则由Home组件定义。这样有利于代码分离,也有利于使用React Router提供的各种API。
Redirect 组件
<Redirect>组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。
<Route path="inbox" component={Inbox}>
{/* 从 /inbox/messages/:id 跳转到 /messages/:id */}
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
现在访问 /inbox/messages/5,会自动跳转到/messages/5。
IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件。
<Route path="/" component={App}>
<IndexRedirect to="/welcome" />
<Route path="welcome" component={Welcome} />
<Route path="about" component={About} />
</Route>
用户访问根路径时,将自动重定向到子组件welcome。
Link
Link组件用于取代<a>元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是<a>元素的React 版本,可以接收Router的状态。
render() {
return <div>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
</div>
}
如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件。
histroy 属性
Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。
history属性,一共可以设置三种值。
- browserHistory
- hashHistory
- createMemoryHistory
如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path
如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径example.com/some/path
但是,这种情况需要对[服务器改造])。