⑤ React 基于react脚手架构建简单项目

本文介绍了React初学者如何使用create-react-app创建项目,组件化开发流程,以及实际构建一个包含评论管理功能的组件应用。通过实例演示了组件拆分、静态与动态组件的实现,以及状态管理和事件处理。
摘要由CSDN通过智能技术生成

查看专栏其它文章:

① React 介绍及JSX简单使用

② React 面向组件编程(state、props、refs)、事件处理

③ React 条件渲染、组件生命周期、表单与事件

④ 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代码

在有了之前的基础后,再简单说一下所谓 组件化编码 的步骤( 编码过程主要看个人习惯 ):

  1. 拆分组件: 拆分界面,抽取组件

  2. 实现静态组件: 使用组件实现静态页面效果

  3. 实现动态组件
    ① 动态显示初始化数据
    ② 交互功能(从绑定事件监听开始)

那无论是如何编写代码,拆分组件都应该是需要的,方便后续创建组件和管理。

在本次练习中,将完成以下界面及其功能。在这里选择将其拆分成以下几个组件。
在这里插入图片描述
通过拆分得到,根组件 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
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值