先要准备环境。搭建一个基于webpack的react环境:Hello ReactJS.
我在想是否应该完整的记录照抄的过程呢。毕竟已经开始一段,前面的要不要补上?回头看以前写过的angularJS的博客,现在完全不会了,太久没用了。所以,还是记录基础以及关注的问题就好。
react的模板文件后缀结尾为.jsx
。
react可以采用html标签拼接的方式定义一个元素。比如:
const element = <h1>Hello, world</h1>;
假设页面有个div:
<div id="root"></div>
那么,reactJS可以这样渲染页面:
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
react-dom
.element
变量就是一个react的元素,一个组件,一个component.ReactDOM.render(reactElement, domElement)
来渲染页面react可以使用一对大括号来包裹变量,与html拼接:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('clock')
);
}
setInterval(tick, 1000);
div
和h1
,h2
拼接的匿名组件。下面实践以上的代码。首先,由于采用单个元素测试,需要修改上次搭建好的环境。
修改webpack.config.js
module.exports = {
- entry: './app/index.js',
+ entry: {
+ app: './app/index.js',
+ clock: './app/components/step1-element.jsx'
+ },
output: {
path: path.resolve(__dirname, 'dist'),
- filename: 'index_bundle.js'
+ filename: '[name].bundle.js',
},
意思是可以渲染多个打包后的js文件。分别定义entry就是需要单独打包的js。在filename就会根据entry的key来生成打包后的文件名。
创建app/components/step1-element.jsx
```js
import React from 'react';
import ReactDOM from 'react-dom';
function Clock(props) {
return (
);
}
function tick() {
ReactDOM.render(<Clock date={new Date()} />, document.getElementById('clock'));
}
setInterval(tick, 1000);
``- functionClock就是一个react component,和前面的element一样,都是react组件. - react component可以写成html标签的方式,但要求方法名必须大写,也即标签名必须大写。就是组件的用法。 - 组件Clock接收一个参数对象props,props的属性可以通过标签上的变量来赋值。比如date就通过标签传入到functionClock里了。由此,像
`这种拼接的标签肯定也是有function的,不过react库已经写好了。
修改app/index.html.添加一个我们用来测试div节点。这里主要用于clock
```diff
+
+
```
然后,运行yarn build
。编译后的dist目录如下:
|____dist | |____app.bundle.js | |____clock.bundle.js | |____index.html | |____index_bundle.js
可以看到定义的两个js都已经生成。而且index.html中也插入:
```
React App
``但发现还多了个
index_bundle.js`,这是我们上一步生成。在本次构建中并没有自动移除。想要自动移除怎么办?
添加webpack plugin: clean-webpack-plugin
yarn add clean-webpack-plugin
修改webpack.config.js
```diff
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: './app/index.html',
filename: 'index.html',
inject: 'body'
});
module.exports = {
entry: {
app: './app/index.js',
clock: './app/components/step1-element.jsx'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
module: {
loaders: [
{ test: /.js\(/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.jsx?\)/, loader: 'babel-loader', exclude: /node_modules/ }
]
},
plugins: [
HtmlWebpackPluginConfig,
new CleanWebpackPlugin(['dist'])
]
};
``重新build.
yarn build`。此时,dist目录下当只有所需要的文件了。
启动html查看效果。这时可以采用webstom或者idea里的用浏览器打开功能,会自动创建的静态服务器。方便简单。也可以安装http-server
。不过,既然用webpack,肯定采用webpack的热编译功能。
yarn start
浏览器访问localhost:8080
就是我们的页面了。
一个值得二级标题的功能。在chrom扩展里搜索React Developer Tools
,添加。然后重新打开我们的页面。看控制台的react节点:
除了上文使用function来创建一个react component。推荐采用es6 class的方式。更加清晰。 由于用到lambda语法糖,需要增加一个新的babel插件:
yarn add babel-plugin-transform-class-properties --dev
然后在.babelrc文件中新增:
"plugins": ["transform-class-properties"]
下面创建app/components/LoginButton.jsx
import React from 'react';
class LoginButton extends React.Component {
handleClick = () => {
console.log("this is ", this);
};
render() {
return (
<button onClick={this.handleClick}>
Click me, auto bind this by lambda
</button>
);
}
}
export default LoginButton;
这里有几个需要注意的地方。
class
来声明一个component,并在结尾处export default
出去。React.Component
以上创建了一个组件LoginButton,我们可以像开始一样直接render到一个dom元素里。也可以直接添加到另一个component组件中。比如搭建环境时给的App组件:
import React from 'react';
import Clock from './Clock.jsx';
import ActionLink from './ActionLink.jsx';
+ import LoginButton from './LoginButton.jsx'
class App extends React.Component {
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>Hello World! Hi ReactJS!</h1>
<div>
<Clock />
<ActionLink />
+ <LoginButton/>
</div>
</div>
);
}
}
export default App;
yarn start
可以观察到页面多了按钮。
最开始的demo Clock中,使用一个时间函数,定时render页面。这种需求可以转换为定时更新状态,由react自动根据状态来渲染页面。对于那个Clock组件来说,唯一变化的就是时间,那么这个时间就是动态的状态。react的component的有个state属性,专门用来传递状态,或者说数据的。当我们需要修改数据的时候,直接修改state就可以了。
新建app/components/Clock.jsx
import React from 'react';
function FormattedDate(props) {
return <h2> It is {props.date.toLocaleTimeString()}. </h2>
}
/**
* @Author Ryan Miao
* @Date 2017/08/02 20:58
*/
class Clock extends React.Component {
constructor(props){
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID)
}
tick() {
this.setState({date: new Date()});
}
render() {
return (
<div>
<h1>This is a clock!</h1>
<FormattedDate date={this.state.date} />
</div>
);
}
}
export default Clock;
lifecycle hooks
。是react组件声明周期前后会调用的方法。componentWillUnmount()会在component移除的时候触发。this.timerID可以直接将属性timerID绑定到this上,这个不需要绑定到state,因为这个和渲染(render)页面无关。组件创建完毕,下面开始使用。使用方式就是转换成标签的方式调用它。
import React from 'react';
+ import Clock from './Clock.jsx';
import ActionLink from './ActionLink.jsx';
import LoginButton from './LoginButton.jsx'
import LoginControl from './LoginControl.jsx'
class App extends React.Component {
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>Hello World! Hi ReactJS!</h1>
<div>
+ <Clock />
<ActionLink />
<LoginButton/>
</div>
<div>
<LoginControl />
</div>
</div>
);
}
}
export default App;
页面这时候就会自动刷新时间了。
React里的属性采用驼峰命名规则,在原来的html中,定义onclick属性:
<button onclick="activateLasers()">
Activate Lasers
</button>
但在react里,必须将onclick改成onClick
<button onClick={activateLasers}>
Activate Lasers
</button>
在原来的html中,可以通过return false的方式阻止默认事件。比如,a标签有href和onClick属性。在html中,我们想要阻止点击的时候跳转到href,那么可以在onClick中返回false
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
这样,你点击a标签后,浏览器地址栏不会有#,如果你不return false,浏览器地址栏就会发生跳转。这是a标签的默认行为。在html中可以通过return false来阻止。但在react中这样做无效。必须使用preventDefault
创建app/components/ActionLink.jsx
import React from 'react';
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log("The link was clicked. PreventDefault event.");
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
export default ActionLink;
然后在App.jsx中引入。刷新页面,点击a标签。观察浏览器地址栏可以发现没有任何变化,证明默认行为被阻止了。如果注释掉e.preventDefault();
,刷新页面,点击a标签,观察地址栏就会发现发生了改变。
接着理解react组件的写法。写一个Toggle按钮,每次点击都切换状态。 创建app/components/Toggle.jsx
import React from 'react';
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: true,
color: 'red'
};
//This bind is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("this=", this);
this.setState(
prevStat => ({
isToggleOn: !prevStat.isToggleOn,
color: prevStat.color==='red'? 'green':'red'
})
);
}
render() {
return (
<button onClick={this.handleClick} style={{background: this.state.color}}>
{this.state.isToggleOn ? 'ON':'OFF'}
</button>
);
}
}
export default Toggle;
另一种方式自动绑定方法成为一个实例,是采用babel-plugin-transform-class-properties
。这个目前还不是es的标准,因为将方法定义为属性这种做法还很有争议。在java8中lambda也是如此,但java8将lambda设定为一等公民,是另一个东西,和成员变量类似。这里,如果使用这个plugin的话,lambda语法糖可以升级为属性,那么就不用绑定this了。
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
还有一种方式是lambda语法,但官方不推荐:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the property initializer syntax, to avoid this sort of performance problem. 建议采用前两种方式。
综合以上的demo。编写新需求。当用户没有登录的时候,显示"Please login",并显示login按钮,当用户登录的时候显示"welcome"和logout按钮。 创建app/components/Greeting.jsx
import React from 'react';
function UserGreeting(props){
return <h1>Welcome back!</h1>
}
function GuestGreeting(props){
return <h1>Please sign up.</h1>
}
function Greeting(props){
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn){
return <UserGreeting/>
}
return <GuestGreeting/>
}
export default Greeting;
创建app/components/LoginControl.jsx
import React from 'react';
import Greeting from "./Greeting.jsx";
function LoginButton(props) {
return (
<button onClick={props.onClick} style={{color: 'white', background:"green"}} >
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick} style={{color: 'white', background:'red'}}>
Logout
</button>
);
}
/**
* @Author Ryan Miao
* @Date 2017/08/02 20:23
*/
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
console.log("Click login");
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
console.log("Click logout");
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div style={{border: '1px solid #000'}}>
<Greeting isLoggedIn={this.state.isLoggedIn}/>
{button}
</div>
);
}
}
export default LoginControl;
在App.jsx中引入
import React from 'react';
import Clock from './Clock.jsx';
import ActionLink from './ActionLink.jsx';
import LoginButton from './LoginButton.jsx'
+ import LoginControl from './LoginControl.jsx'
import Toggle from './Toggle.jsx'
class App extends React.Component {
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>Hello World! Hi ReactJS!</h1>
<div>
<Clock />
<ActionLink />
<LoginButton/>
<Toggle />
</div>
+ <div>
+ <LoginControl />
+ </div>
</div>
);
}
}
export default App;