02_react实例_笔记

1. 使用React脚手架创建一个React应用

1). react脚手架

1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
	* 包含了所有需要的配置
	* 指定好了所有的依赖
	* 可以直接安装/编译/运行一个简单效果
2. react提供了一个专门用于创建react项目的脚手架库: create-react-app
3. 项目的整体技术架构为: react + webpack + es6  + babel + eslint

2). 创建项目并启动

npm install -g create-react-app
create-react-app react-app
cd react-app
npm start

3). 使用脚手架开发的项目的特点

模块化: js是一个一个模块编写的
组件化: 界面是由多个组件组合编写实现的
工程化: 实现了自动构建/运行/打包的项目

4). 组件化编写项目的流程

拆分组件
实现静态组件--->静态页面
实现动态组件
	动态显示初始化数据
	交互

2. app1: 实现一个评论管理功能

1). 拆分组件:

应用组件: App 父组件,定义了add和delete方法 在子组件中改变父组件的状态:状态在哪个组件,更新状态的行为(方法)就应该定义在哪个组件	父组件定义函数,传递给子组件,子组件调用
* state: comments/array
添加评论组件: CommentAdd
* state: username/string, content/string
* props: add/func
评论列表组件: CommentList
* props: comment/object, delete/func, index/number
评论项组件: CommentItem
* props: comments/array, delete/func

2). 确定组件的state和props:

App: 
	* state: comments/array
CommentAdd
	* state: username/string, content/string
	* props: add/func
commentList
  	* props: comments/array, delete/func
CommentItem
	* props: comment/object, delete/func, index/number

3). 编写静态组件

拆分页面
拆分css

4). 实现动态组件

1. 动态展示初始化数据
  * 初始化状态数据
  * 传递属性数据
2. 响应用户操作, 更新组件界面
  * 绑定事件监听, 并处理
  * 更新state

5). 代码回顾分析

5.1 App.jsx

import React, {Component} from "react";

import CommentAdd from "../comment-add/comment-add";
import CommentList from "../comment-list/comment-list";

export default class App extends Component { //默认暴露,

//  复杂写法:
/*  constructor(props) {
    super(props);
    this.state = {
      comments : [
        {username:'Tom',content:'React挺好的!'},
        {username:'Mary',content:'React太难了!'}
      ]
    }
  }*/

  //简单方法
  //给组件对象指定state属性,不是这个App类,是这个this
  state = {
    comments : [
      {username:'Tom',content:'React挺好的!'},
      {username:'Mary',content:'React太难了!'}
    ]
  }

  addComment = (comment) =>{
    const {comments} = this.state
    comments.unshift(comment)
    //更新状态
    this.setState({comments})
  }

  deleteComment = (index) => {
    const {comments} = this.state
    comments.splice(index,1)//删除一个
    //comments.splice(index,0,{})添加一个
    //comments.splice(index,1,{})替换一个
    //更新状态
    this.setState({comments})
  }

  render() {

    const {comments} = this.state

    return (
      <div id="app">
        <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 addComment ={this.addComment}/>
            <CommentList comments={comments} deleteComment={this.deleteComment}/>
          </div>
        </div>
      </div>
    )
  }
}

5.2 comment-list.jsx

import React, {Component} from "react";
//这个包需要下载,命令 npm install --save prop-types
import PropTypes from 'prop-types'

import CommentItem from "../comment-item/comment-item";
import './commentList.css'

export default class CommentList extends Component{ //默认暴露,

  //不加static是给组件对象添加,加上是给组件类CommentList指定属性
  static propTypes = {
    comments: PropTypes.array.isRequired,
    deleteComment:PropTypes.func.isRequired
    //delete: PropTypes.func.isRequired
  }

render() {
    const {comments,deleteComment} = this.props
  //let comments = this.props.comments
  //计算出是否显示
  const display = comments.length === 0 ? 'block' : 'none'
  return (
    <div className="col-md-8">
      <h3 className="reply">评论回复:</h3>
      <h2 style={{display}}>暂无评论,点击左侧添加评论!!!</h2>
      <ul className="list-group">
        {
          comments.map((comment,index)=><CommentItem comment={comment} key={index}
                                     index={index}  deleteComment={deleteComment}/>)
        }
      </ul>
    </div>
  )
}
}
//复杂方法
/*  CommentList.propTypes = {
  comments: PropTypes.array.isRequired,
  delete: PropTypes.func.isRequired
}*/

5.3 comment-item.jsx

import React, {Component} from "react";
import PropTypes from 'prop-types'

import './commentItem.css'

export default class CommentItem extends Component{ //默认暴露,

  static propTypes = {
    comment:PropTypes.object.isRequired,
    deleteComment:PropTypes.func.isRequired,
    index:PropTypes.number.isRequired
  }

  handleClick = () =>{
    const{comment,deleteComment,index} = this.props
    //提示
    if(window.confirm(`确定删除${comment.username}的评论吗?`)){
      deleteComment(index)
    }
  }

  render() {
    //let comment = this.props.comment
    const {comment} = this.props
    //alert({comment}.length)
    //alert(Object.keys({comment}).length)
    //console.log(Object.keys({comment}))
    return (
      <li className="list-group-item">
        <div className="handle">
          <a href="javascript:;" onClick={this.handleClick}>删除</a>
        </div>
        <p className="user"><span>{comment.username}</span><span>说:</span></p>
        <p className="centence">{comment.content}</p>
      </li>
    )
  }
}

5.4 comment-add.jsx

import React, {Component} from "react";
import PropTypes from 'prop-types'

export default class CommentAdd extends Component{ //默认暴露,

  static propTypes = {
    addComment:PropTypes.func.isRequired
  }

  state = {
    username:'',
    content:''
  }

//一直bind过于麻烦,可以使用箭头函数,现在箭头函数没有自己的this,就看外围this正好是组件对象
  handleSubmit = () =>{
    //收集数据,并封装成comment对象
    const comment = this.state
    //更新状态
    this.props.addComment(comment)
    //清除输入数据
    this.setState({
        username:'',
        content:''
      }
    )
  }

  handleNameChange = (event) =>{
    const username = event.target.value
    this.setState({username})
  }

  handleContentChange = (event) =>{
    const content = event.target.value
    this.setState({content})
  }

  render() {

    const {username,content} = this.state

    return (
      <div className="col-md-4" >
        <form className="form-horizontal">
          <div className="form-group">
            <label>用户名</label>
            <input type="text" className="form-control" placeholder="用户名"
                   value={username} onChange={this.handleNameChange}/>
          </div>
          <div className="form-group">
            <label>评论内容</label>
            <textarea className="form-control" rows="6" placeholder="评论内容"
                   value={content} onChange={this.handleContentChange}></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.handleSubmit}>提交</button>
            </div>
          </div>
        </form>
      </div>
    )
  }
}

5.5 index.js

import React from "react";
import ReactDOM from "react-dom"
import App from './components/app/app'
//引入自定义模块必须以./或者../开头,前者为当前目录,后者为上级目录

ReactDOM.render(<App/>, document.getElementById('root'))

3. app2: 实现github用户搜索功能

1). react应用中的ajax请求

axios: 包装XMLHttpRequest对象, promise风格, 支持浏览端/node服务器端
fetch: 浏览器内置语法, promise风格, 老浏览器不支持, 可以引入fetch.js兼容包

2). 拆分组件

App
	* state: searchName/string
Search
  	* props: setSearchName/func
List
  	* props: searchName/string
  	* state: firstView/bool, loading/bool, users/array, errMsg/string

3). 编写组件

编写静态组件
编写动态组件
	componentWillReceiveProps(nextProps): 监视接收到新的props, 发送ajax
	使用axios库发送ajax请求

4).代码回顾分析

4.1 app.jsx

import React from 'react'
import Search from './search'
import Main from './main'

export default class App extends React.Component {

  state = {
    searchName: ''
  }

  setStateName = (searchName) => {
    //不行,这不是更新state状态
    // this.setStateName = searchName
    this.setState({searchName:searchName})
    //或者this.setState({searchName}) 毕竟只有一个元素
  }

  render() {
    return (
      <div id="app">
      <div className="container">
        <Search  setSearchName={this.setStateName}/>
        <Main searchName={this.state.searchName}/>
      </div>
      </div>
    )
  }

}

4.2 main.jsx

/**
 * 下部的用户列表模块
 */
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'

export default class Main extends React.Component {

  static propTypes = {
    searchName: PropTypes.string.isRequired
  }

  state = {
    initView: true,
    loading: false,
    users: null,
    errorMsg: null
  }

  //当组件接收到新的props时回调
  componentWillReceiveProps(newProps) {//指定了新的searchName,需要请求
    const {searchName} = newProps
    //更新状态(请求中)
    this.setState({
      initView: false,
      loading: true
    })
    //发ajax请求
    const url = `https://api.github.com/search/users?q=${searchName}`
    axios.get(url)
      .then(response => {
        //得到响应数据
        const result = response.data
        //console.log(result)
        //获取的result的item属性太多,通过map方法重新建立一个只有所需属性的数组
        const users = result.items.map(item => {
          return {name: item.login, url: item.html_url, avatarUrl: item.avatar_url}
        })
        //更新状态(成功)
        this.setState({loading: false, users})
      })
      .catch(error => {
        //更新状态(失败)
        this.setState({loading: false, errorMsg: error.message})
      })
  }

  render() {
    const {initView, loading, users, errorMsg} = this.state
    const {searchName} = this.props
    console.log("render():" + searchName)


    if (initView) {
      return <h2>请输入关键词进行搜索 {searchName}</h2>
    } else if (loading) {
      return <h2>正在请求中...</h2>
    } else if (errorMsg) {
      return {errorMsg}
    } else {
      return (
        <div className="row">
          {
            users.map((user, index) => ( //这里用小括号可以直接跟,大括号还得加return
              <div className="card" key={index}>
                <a href={user.url} target="_blank">
                  <img src={user.avatarUrl} style={{width: 100}}/>
                </a>
                <p className="card-text">{user.name}</p>
              </div>
            ))
          }
        </div>
      )
    }
  }
}

4.3 search.jsx

/**
 * 上部的搜索模块
 */
import React, {Component} from 'react'
import PropTypes from 'prop-types'

export default class Search extends Component {

  static propTypes = {
    setSearchName: PropTypes.func.isRequired
  }

  search = () => {
    //得到输入的关键字
    const searchName = this.input.value.trim()
    if(searchName){
      //搜索
      this.props.setSearchName(searchName)
    }
  }

  render() {
    return (

      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input type="text" placeholder="enter the name you search" ref={input => this.input = input}/>
          <button onClick={this.search}>Search</button>
        </div>
      </section>
    )
  }
}

4. 组件间通信总结

1). 方式一: 通过props传递

共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
一般数据-->父组件传递数据给子组件-->子组件读取数据
函数数据-->子组件传递数据给父组件-->子组件调用函数
问题: 多层传递属性麻烦, 兄弟组件通信不方便

2). 方式二: 使用消息订阅(subscribe)-发布(publish)机制: 自定义事件机制

工具库: PubSubJS
下载: npm install pubsub-js --save
使用: 
  import PubSub from 'pubsub-js' //引入
  PubSub.subscribe('delete', function(msg, data){ }); //订阅
  PubSub.publish('delete', data) //发布消息
优点: 可以支持任意关系组件之间的通信

3). 事件监听理解

1. DOM事件
	* 绑定事件监听
		* 事件名(类型): 只有有限的几个, 不能随便写
		* 回调函数
	* 用户操作触发事件(event)
		* 事件名(类型)
		* 数据
2. 自定义事件
	* 绑定事件监听
		* 事件名(类型): 任意
		* 回调函数: 通过形参接收数据, 在函数体处理事件
	* 触发事件(编码)
		* 事件名(类型): 与绑定的事件监听的事件名一致
		* 数据: 会自动传递给回调函数

5. ES6新语法总结

定义变量/常量: const/let
解构赋值: let {a, b} = this.props   import {aa} from 'xxx'
对象的简洁表达: {a, b}
箭头函数: 
	组件的自定义方法: xxx = () => {}
	map/filter的回调方法: (item, index) => {}
	优点:
		* 简洁
		* 没有自己的this,使用引用this查找的是外部this
扩展运算符: ...
	拆解对象:  const MyProps = {}, <Xxx {...MyProps}>
类: class/extends/constructor/super
ES6模块化: export default | import

6. 项目打包运行

npm run build  //生成打包文件
npm install -g serve  //全局下载服务器包
serve build  //通过服务器命令运行打包项目
访问: http://localhost:5000  //浏览器访问

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值