查看专栏其它文章:
② React 面向组件编程(state、props、refs)、事件处理
④ React 列表与Keys、虚拟DOM相关说明、AJAX
本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷
构建项目
所谓“脚手架”,就是用来帮助程序员快速创建一个基于xxx库的模板项目。它包含了所有需要的配置,指定好了所有的依赖,并可以直接安装/编译/运行一个简单效果。react提供了一个用于创建react项目的脚手架库: create-react-app
在继续下面的内容前,请确保电脑中已安装了Node.JS。如果未安装,可直接去其官网下载。安装步骤很简单。
如果对Node.JS感兴趣,可以查阅我的文章:Node.JS 介绍及基础知识(模块化简介、NPM用法、Buffer缓冲区、fs文件系统)
检测是否安装成功:
创建项目
以下步骤可以在命令行中执行,也可以选择在各个开发工具中执行。
开发工具的命令行在左下角的Terminal ( Webstorm 、IDEA )
步骤一: npm install -g create-react-app
步骤二:create-react-app hello-react (这可能需要很长时间,请耐心等待)
随后它也会告诉你相关命令。( 我们想运行项目之后输入 npm start 即可 )
步骤三:cd hello-react
步骤四:npm start
然后就会自动跳转进页面。如果未跳转,在命令行中点击链接也可以。
到这里基础的项目已经构建完成。想运行项目就 npm start。
补充说明: 对于Google Chrome,如果新建的react项目,执行npm start后控制台报错: Uncaught TypeError: Cannot read property ‘forEach‘ of undefined,看该篇文章就可解决问题:解决办法
如果开发工具不识别 JSX (出现红色波浪线),我们可以这样设置:
项目结构
建好项目后,简单介绍一下项目结构:
对于普通的学习者来说,我们主要知道 src 中的文件该如何操作即可。(将在后续叙述)
简单介绍一下还应该了解的部分内容:
gitignore 会在打包的时候帮我们忽略一些不想去编译打包的文件,比如 node_modules、debug、各种开发工具的文件夹(比如.idea)等等。
package.json 主要提供项目的一些相关标识(必不可少的就是name和version)、记录一些依赖信息(必不可少的就是依赖包和它的版本号)、记录当前项目如何运行和打包。
编写应用
项目构建好后,清空src中的初始文件,然后去建我们自己的文件。
首先我们可以建一个components文件夹用来存放组件,然后其中的所有文件全是jsx文件,用来表示它是组件。然后在src层存放css文件、js文件等。(详细结构需要看个人习惯)
jsx组件中应该写什么内容?
和之前学习的语法一样,但是需要import React,还需要 export 暴露出去。
import React from 'react'
export default class App extends React.Component {
render () {
return (
<div></div>
)
}
}
这就是最基本的结构。如果我们想引入自己的资源文件,依然选用 import 导入即可。现在举个简单的例子,展示一张图片:( app.jsx )
import React from 'react'
import logo from '../logo.svg'
export default class App extends React.Component {
render () {
return (
<div>
<img src={logo} alt="logo"/>
</div>
)
}
}
组件设置好了之后,还需要设置其他内容:
入口文件 index.js 中应该写什么内容?
它的写法比较固定:import React 和 ReactDOM ,然后 import 组件,最后和之前语法一样,用 render 渲染。
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app'
ReactDOM.render(<App />, document.getElementById('root'))
在这里推荐 jsx 组件名小写,但是导入时首字母大写。
而在这里为什么 document.getElementById('root')
,id 为什么是 root ?
这是因为在 public 的 index.html(主页面)中,根标签 id 是 root。
通常情况下,我们不修改 index.html 中的内容。
现在就可以使用 npm start 看一下效果了。
补充说明: 在运行时为什么会去选择 public 下的 index.html 文件,又为什么会选择 src 文件夹下的 index.js 作为入口文件呢?
这是因为 React 帮我们配置好了这部分内容。
在每个项目中,都可以看到 node_modules 文件夹,里面存放了项目需要的所有依赖。在这里就有我们想要的答案。
在最初创建项目时,create-react-app 把 webpack、babel 等配置都封装到了依赖项目 react-script 中,所以你会发现在项目的 package.json 中明明没有多少依赖包,结果 node_modules 里有那么多依赖的原因。你可以在项目下运行 npm run eject,被隐藏的配置文件就会暴露到项目根路径下。当你npm start
时,它会启动webpack-dev-server,会加载react-script项目config文件夹下的配置(paths.js)
当我们打开 paths.js 就可以看到,在里面定义了请求的默认转发路径是public文件夹,自然就找到了public下的index.html,同理index.js也是。
如果想要添加样式,就可以在组件中添加类名,然后在 index.css 中添加样式。同时不要忘记在入口文件 index.js 中导入样式
( index.css )
.logo {
width: 200px;
height: 200px;
}
.title{
color: red;
font-size: 25px;
}
( app.jsx )
import React from 'react'
import logo from '../logo.svg'
export default class App extends React.Component {
render () {
return (
<div>
<img className='logo' src={logo} alt="logo"/>
<p className='title'>react app 组件</p>
</div>
)
}
}
( index.js )
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
在这里需要说明的是,在组件render中放入html时:
在html中,设置类是<p class="xxx"></p>
,
现在要将它们全部改成<p className="xxx"></p>
如果在html中涉及到了style:<p style='display:none'></p>
,
现在要将它们全部改成<p style={{display: 'none'}}></p>
除此以外,项目能够实时更新(即 只要一次 npm start ,之后每次修改内容,项目都会自动重新编译打包)
练习:评论管理
( 以下内容中将不包括CSS代码 )
在有了之前的基础后,再简单说一下所谓 组件化编码 的步骤( 编码过程主要看个人习惯 ):
-
拆分组件: 拆分界面,抽取组件
-
实现静态组件: 使用组件实现静态页面效果
-
实现动态组件
① 动态显示初始化数据
② 交互功能(从绑定事件监听开始)
那无论是如何编写代码,拆分组件都应该是需要的,方便后续创建组件和管理。
在本次练习中,将完成以下界面及其功能。在这里选择将其拆分成以下几个组件。
通过拆分得到,根组件 app 和它的子组件 add 和 list 。list 组件的子组件为 item。因此src结构:
因为 app 是根组件,所以在 index.js 中:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app/app'
ReactDOM.render(<App/>, document.getElementById('root'))
整体的css文件可以放在这里:
调用的时候去 index.html 中:<link rel="stylesheet" href="/css/xxx.css">
即可。
拆分好之后就需要考虑每个组件中的内容:在项目中涉及到了哪些属性和方法,分别放在哪个组件中比较合适。显然在本例中,所有属性和方法全部放在 app 组件中,再传递出去最合适。因为 app 组件是根组件。如果 add 组件存放用户名等属性,需要在 list 中展示,那还需要先传递给 app 组件再传递给 list 组件。对于方法也是一样的,因为如果想要提交,需要对属性做添加操作,还是需要找到属性。因此放在根组件中合适。
(先不考虑其他传递方式,就是最基本的传递。组件间通信将在下篇文章叙述)
构思好之后,第二步,所谓的静态组件,就是先去实现 html 和 css 部分,看能否正常显示,因为如果不能正常显示,之后做的一切也都没意义。当页面能显示后,我们就可以做第三步,动态组件:定时器、提交效果、删除效果等等效果。
对于这整部分内容的代码,在使用上全是以前文章中用到的用法。为了更加直观和简洁,在这里就直接演示最终代码了。和以前的唯一区别,需要注意的是:不要忘记 import 。
app.jsx
import React from 'react'
import CommentAdd from '../comment-add/comment-add'
import CommentList from '../comment-list/comment-list'
class App extends React.Component {
//构造函数
constructor (props) {
super(props)
this.state = { //初始化
comments: []
}
//绑定 add不用绑是因为使用了箭头函数
this.delete = this.delete.bind(this)
}
componentDidMount () {
//模拟异步获取数据
setTimeout(() => {
const comments = [
{
username: "Tom",
content: "ReactJS好难啊!",
id: Date.now()
},
{
username: "JACK",
content: "ReactJS还不错!",
id: Date.now() + 1
}
]
this.setState({comments})
}, 1000)
}
add = (comment) => {
let comments = this.state.comments
comments.unshift(comment)
this.setState({ comments })
}
delete (index) {
let comments = this.state.comments
comments.splice(index, 1)
this.setState({ comments })
}
render () {
return (
<div>
<header className="site-header jumbotron">
<div className="container">
<div className="row">
<div className="col-xs-12">
<h1>请发表对React的评论</h1>
</div>
</div>
</div>
</header>
<div className="container">
<CommentAdd add={this.add}/>
<CommentList comments={this.state.comments} delete={this.delete}/>
</div>
</div>
)
}
}
export default App
comment-add.jsx
import React from 'react'
import PropTypes from 'prop-types'
class CommentAdd extends React.Component {
//构造函数
constructor (props) {
super(props)
this.state = { //初始化
username: '',
content: ''
}
//绑定
this.addComment = this.addComment.bind(this)
this.changeUsername = this.changeUsername.bind(this)
this.changeContent = this.changeContent.bind(this)
}
addComment () {
// 根据输入的数据创建评论对象
let { username, content } = this.state
let comment = { username, content }
// 添加到comments中, 更新state
this.props.add(comment)
// 清除输入的数据
this.setState({
username: '',
content: ''
})
}
changeUsername (event) {
this.setState({
username: event.target.value
})
}
changeContent (event) {
this.setState({
content: event.target.value
})
}
render () {
return (
<div className="col-md-4">
<form className="form-horizontal">
<div className="form-group">
<label>用户名</label>
<input type="text" className="form-control" placeholder="用户名"
value={this.state.username} onChange={this.changeUsername}/>
</div>
<div className="form-group">
<label>评论内容</label>
<textarea className="form-control" rows="6" placeholder="评论内容"
value={this.state.content} onChange={this.changeContent}></textarea>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-default pull-right" onClick={this.addComment}>提交</button>
</div>
</div>
</form>
</div>
)
}
}
CommentAdd.propTypes = {
add: PropTypes.func.isRequired
}
export default CommentAdd
comment-list.jsx
import React from 'react'
import PropTypes from 'prop-types'
import CommentItem from '../comment-item/comment-item'
import './commentList.css'
class CommentList extends React.Component {
constructor (props) {
super(props)
}
render () {
let comments = this.props.comments
let display = comments.length > 0 ? 'none' : 'block'
return (
<div className="col-md-8">
<h3 className="reply">评论回复:</h3>
<h2 style={{ display: display }}>暂无评论,点击左侧添加评论!!!</h2>
<ul className="list-group">
{
comments.map((comment, index) => {
console.log(comment)
return <CommentItem comment={comment} key={index} index={index} delete={this.props.delete}/>
})
}
</ul>
</div>
)
}
}
CommentList.propTypes = {
comments: PropTypes.array.isRequired,
delete: PropTypes.func.isRequired
}
export default CommentList
comment-item.jsx
import React from 'react'
import PropTypes from 'prop-types'
import './commentItem.css'
class CommentItem extends React.Component {
constructor (props) {
super(props)
this.deleteComment = this.deleteComment.bind(this)
}
deleteComment () {
let username = this.props.comment.username
if (window.confirm(`确定删除${username}的评论吗?`)) {
this.props.delete(this.props.index)
}
}
render () {
let comment = this.props.comment
return (
<li className="list-group-item">
<div className="handle">
<a href="javascript:" onClick={this.deleteComment}>删除</a>
</div>
<p className="user"><span >{comment.username}</span><span>说:</span></p>
<p className="centence">{comment.content}</p>
</li>
)
}
}
CommentItem.propTypes = {
comment: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
delete: PropTypes.func.isRequired
}
export default CommentItem