本文共 12777 字,大约阅读时间需要 42 分钟。
不晓得啥情况,markdown在csdn识别错误?排版后面的代码被破坏了,正确的排版:
10000个小时策略来学习,因为笨。先照着官方文档敲一遍,写一遍。
先要准备环境。搭建一个基于webpack的react环境:.
我在想是否应该完整的记录照抄的过程呢。毕竟已经开始一段,前面的要不要补上?回头看以前写过的angularJS的博客,现在完全不会了,太久没用了。所以,还是记录基础以及关注的问题就好。
react的模板文件后缀结尾为.jsx
。
react可以采用html标签拼接的方式定义一个元素。比如:
const element =Hello, world
;
假设页面有个div:
那么,reactJS可以这样渲染页面:
const element =Hello, world
;ReactDOM.render( element, document.getElementById('root'));
react-dom
.element
变量就是一个react的元素,一个组件,一个component.ReactDOM.render(reactElement, domElement)
来渲染页面react可以使用一对大括号来包裹变量,与html拼接:
function tick() { const element = (); ReactDOM.render( element, document.getElementById('clock') );}setInterval(tick, 1000);Hello, world!
It is {new Date().toLocaleTimeString()}.
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 (ReactDOM.render(, document.getElementById('clock'));
}
setInterval(tick, 1000); ``- function
Clock就是一个react component,和前面的
element一样,都是react组件. - react component可以写成html标签的方式,但要求方法名必须大写,也即标签名必须大写。
就是组件的用法。 - 组件
Clock接收一个参数对象
props,
props的属性可以通过标签上的变量来赋值。比如
date就通过标签传入到function
Clock里了。由此,像
修改app/index.html.添加一个我们用来测试div节点。这里主要用于clock
然后,运行yarn build
。编译后的dist目录如下:
|____dist | |____app.bundle.js | |____clock.bundle.js | |____index.html | |____index_bundle.js
可以看到定义的两个js都已经生成。而且index.html中也插入: ``` ``但发现还多了个
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 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/ }, { 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 ( ); }}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 (); }}export default App;Hello World! Hi ReactJS!
+
yarn start
可以观察到页面多了按钮。
最开始的demo Clock中,使用一个时间函数,定时render页面。这种需求可以转换为定时更新状态,由react自动根据状态来渲染页面。对于那个Clock组件来说,唯一变化的就是时间,那么这个时间就是动态的状态。react的component的有个state属性,专门用来传递状态,或者说数据的。当我们需要修改数据的时候,直接修改state就可以了。
新建app/components/Clock.jsx
import React from 'react';function FormattedDate(props) { returnIt is {props.date.toLocaleTimeString()}.
}/*** @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 (); }}export default Clock;This is a clock!
this.setState({})是唯一能修改state的方式,通过this.state={}的做法无效。另外,setState是一个merge的异步操作。merge是说,每次set的时候,只会修改指定的变量,不会整体替换。异步是说不能直接this.state.xx来操作属性,因为有可能你调用this.state.xx来获取xx的值的时候,前一次的setState还没执行完。如果想要同步的修改state里的属性,可以采用第二种方式:
// Correctthis.setState((prevState, props) => ({ counter: prevState.counter + props.increment}));
接受两个参数,第一个是state,第二个是props。这两个变量会在最后一次修改结束后自动注入。所以就可以放心的设置state的属性了。
lifecycle hooks
。是react组件声明周期前后会调用的方法。componentWillUnmount()会在component移除的时候触发。this.timerID可以直接将属性timerID绑定到this上,这个不需要绑定到state,因为这个和渲染(render)页面无关。FormattedDate是我们抽出来的专门显示时间的组件,date是它的一个props.
组件创建完毕,下面开始使用。使用方式就是转换成标签的方式调用它。
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 (); }}export default App;Hello World! Hi ReactJS!
+
页面这时候就会自动刷新时间了。
React里的属性采用驼峰命名规则,在原来的html中,定义onclick属性:
但在react里,必须将onclick改成onClick
在原来的html中,可以通过return false的方式阻止默认事件。比如,a标签有href和onClick属性。在html中,我们想要阻止点击的时候跳转到href,那么可以在onClick中返回false
这样,你点击a标签后,浏览器地址栏不会有#,如果你不return false,浏览器地址栏就会发生跳转。这是a标签的默认行为。在html中可以通过return false来阻止。但在react中这样做无效。必须使用preventDefault
import React from 'react';function ActionLink() { function handleClick(e) { e.preventDefault(); console.log("The link was clicked. PreventDefault event."); } return ( Click me );}export default ActionLink;
然后在App.jsx中引入。刷新页面,点击a标签。观察浏览器地址栏可以发现没有任何变化,证明默认行为被阻止了。如果注释掉e.preventDefault();
,刷新页面,点击a标签,观察地址栏就会发现发生了改变。
接着理解react组件的写法。写一个Toggle按钮,每次点击都切换状态。
创建app/components/Toggle.jsximport 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 ( ); }}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 ( ); }}
还有一种方式是lambda语法,但官方不推荐:
class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // This syntax ensures `this` is bound within handleClick return ( ); }}
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.jsximport React from 'react';function UserGreeting(props){ returnWelcome back!
}function GuestGreeting(props){ returnPlease sign up.
}function Greeting(props){ const isLoggedIn = props.isLoggedIn; if (isLoggedIn){ return} return }export default Greeting;
创建app/components/LoginControl.jsx
import React from 'react';import Greeting from "./Greeting.jsx";function LoginButton(props) { return ( );}function LogoutButton(props) { return ( );}/*** @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 =; } else { button = ; } return ( ); }}export default LoginControl;{button}
在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 (); }}export default App;Hello World! Hi ReactJS!
+ ++
本文转自Ryan.Miao博客园博客,原文链接:http://www.cnblogs.com/woshimrf/p/react-tutorial-1.html,如需转载请自行联系原作者