React基础 - JSX、组件相关(列表、通信等)、state

2020-07-26  本文已影响0人  闪电西兰花
npx create-react-app my-react
cd my-app
npm start
// package.json
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1"
  },
// 逻辑层
import React from 'react';
// 渲染层
import ReactDOM from 'react-dom';

// 默认使用严格模式,这里绑定根节点的语法可以参考vue的语法去理解
ReactDOM.render(
  <React.StrictMode>
    <App />,
  </React.StrictMode>,
  document.getElementById('root')
);
// App.js
import React from 'react';
import logo from './logo.svg';
import './App.css'
 
function formatName (user) {
   return `${user.firstName},${user.lastName}`
}

class App extends React.Component {
  render () {

    const NAME = 'jerry'
    const USER = {firstName: 'tom', lastName: 'jerry'}
    let jsx = <p>hello,react</p>

    return (
      <div className="App">
        {/* 合法的js表达式 */}
        {/* for循环、条件语句都是不可以的 */}

        <h1>{NAME}</h1>
        <h2>{formatName(USER)}</h2>
  
        {/* 属性 */}
        {/* 这里的style是动态语法,style={},里面的值是个对象*/}
        <img src={logo} style={{width: '200px'}} alt="" />
  
        {/* jsx也是表达式 */}
        {jsx}
      </div>
    );
  }
}
export default App;
// src/components/CompType.js
import React from 'react';

// 函数类型组件
export function Welcome1 (props) {
  return (
    <div>
      Welcome1, {props.name}
    </div>
  )
}

// 类组件
export class Welcome2 extends React.Component{
  render(){
    return <div>Welcome2, {this.props.name}</div>
  }
} 
// App.js 添加以下内容
import {Welcome1, Welcome2}  from './components/CompType'

class App extends React.Component {
  render () {
    return (
      <div className="App">
        
        {/* 组件使用 */}
        <Welcome1 name="name1"></Welcome1>
        <Welcome2 name="name2"></Welcome2>
      
      </div>
    )
  }
}
// src/components/Clock.js
import React from 'react';

export class Clock extends React.Component{

  // 组件状态
  state = {
    date: new Date()
  }

  // 组件挂载之后
  componentDidMount () {
    this.timer = setInterval(() => {
      this.setState({
        date: new Date()
      })
    }, 1000)
  }

  // 组件即将销毁
  compoentWillUnmount () {
    clearInterval(this.timer);
  }

  render(){
    return (
      <div>
        {/* 输出当前组件某个状态的值 */}
        {this.state.date.toLocaleTimeString()}
      </div>
    )
  }
} 
// src/components/StateTest.js
import React from 'react';

export class StateTest extends React.Component{
  state = {
    count: 1
  }

  // 组件挂载之后
  componentDidMount () {
    // 1. 直接修改state数据无效,必须使用setState
    // this.state.count += 10
  
    // 2. setState批量执行
    // 写3遍同样的对count的操作,setState只会执行一次,setState会进行队列排列,对方法进行整合,相同的操作会被合并
    // this.setState(obj, cb)
    // 这里执行完毕显示的值为11
    this.setState({count: this.state.count + 10});
    this.setState({count: this.state.count + 10});
    this.setState({count: this.state.count + 10});

    // 3.如果前后的值是有关联的,比如后面的值依赖于之前的值,可以用函数形式写,prevState可以获取到已经被操作之后的值
    // this.setState(fn, cb)
    // 这里执行完毕显示的值为41
    this.setState(prevState => ({
      count: prevState.count + 10
    }))
    this.setState(prevState => ({
      count: prevState.count + 10
    })) 
    this.setState(prevState => ({
      count: prevState.count + 10
    }))
  }

  render(){
    return (
      <div>
        {this.state.count}
      </div>
    )
  }
} 
以下通过一个购物车组件展示组件的条件渲染、列表渲染、输入框操作、事件操作、组件间通信
// src/components/CartSample.js
import React from 'react';

export class CartSample extends React.Component{
  
  // 状态state初始化一般放在构造器中
  constructor (props) {
    super(props);
    this.state = {}

  render(){
    // 判断父组件是否传入title属性
    let title = this.props.title ? <h5>{this.props.title}</h5> : null
    return (
      <div>
        {/* 条件渲染 */}

        {/* 写法1 */}
        {title}

        {/* 写法2 当this.props.title为空就不再继续执行 */}
        {this.props.title && <h5>{this.props.title}</h5>}
      </div>
    )
  }
}
// App.js  引入CartSample组件
<CartSample title="this is cartsample title"></CartSample>
// src/components/CartSample.js
import React from 'react';

export class CartSample extends React.Component{
  
  // 状态state初始化一般放在构造器中
  constructor (props) {
    super(props);
    this.state = {
      goods: [
        {id: 1, name: 'web'},
        {id: 2, name: 'ios'},
        {id: 3, name: 'pc'},
        {id: 4, name: 'mobile'},
      ]
    }

  render(){
    // 判断父组件是否传入title属性
    let title = this.props.title ? <h5>{this.props.title}</h5> : null
    return (
      <div>
        {/* 条件渲染 */}

        {/* 写法1 */}
        {title}

        {/* 写法2 当this.props.title为空就不再继续执行 */}
        {this.props.title && <h5>{this.props.title}</h5>}

        {/* 列表渲染 */}
        <ul>
          {this.state.goods.map(good => (
            <li key={good.id}>
              {good.name}
            </li>
          ))}
        </ul>

      </div>
    )
  }
}
// src/components/CartSample.js
import React from 'react';

export class CartSample extends React.Component{
  
  // 状态state初始化一般放在构造器中
  constructor (props) {
    super(props);
    this.state = {
      goods: [
        {id: 1, name: 'web'},
        {id: 2, name: 'ios'},
        {id: 3, name: 'pc'},
        {id: 4, name: 'mobile'},
      ],
      // 新增一个状态用于存储输入框的值
      text: '',
    }

  // 这里一定要写成箭头函数,因为这里的方法在调用时会影响this指向
  // 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
  textChange = (evt) => {
    this.setState({text: evt.target.value})
  }

  render(){
    // 判断父组件是否传入title属性
    let title = this.props.title ? <h5>{this.props.title}</h5> : null
    return (
      <div>
        {/* 条件渲染 */}

        {/* 写法1 */}
        {title}

        {/* 写法2 当this.props.title为空就不再继续执行 */}
        {this.props.title && <h5>{this.props.title}</h5>}

        {/* 输入框操作 */}
        <div>
          {/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
           {/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
          <input type="text" value={this.state.text} onChange={this.textChange}/>
        </div>

        {/* 列表渲染 */}
        <ul>
          {this.state.goods.map(good => (
            <li key={good.id}>
              {good.name}
            </li>
          ))}
        </ul>

      </div>
    )
  }
}
// src/components/CartSample.js
import React from 'react';

export class CartSample extends React.Component{
  
  // 状态state初始化一般放在构造器中
  constructor (props) {
    super(props);
    this.state = {
      goods: [
        {id: 1, name: 'web'},
        {id: 2, name: 'ios'},
        {id: 3, name: 'pc'},
        {id: 4, name: 'mobile'},
      ],
      // 新增一个状态用于存储输入框的值
      text: '',
    }

  // 这里一定要写成箭头函数,因为这里的方法在调用时会影响this指向
  // 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
  textChange = (evt) => {
    this.setState({text: evt.target.value})
  }

  // 添加至商品列表
  addGood = () => {
    this.setState(prevState => {
      // 返回一个全新的要更新的数组,而不是在原数组上操作数据
      return {
        goods: [...this.state.goods, {id: prevState.goods.length + 1, name: prevState.text}]
      }
    })
  }

  render(){
    // 判断父组件是否传入title属性
    let title = this.props.title ? <h5>{this.props.title}</h5> : null
    return (
      <div>
        {/* 条件渲染 */}

        {/* 写法1 */}
        {title}

        {/* 写法2 当this.props.title为空就不再继续执行 */}
        {this.props.title && <h5>{this.props.title}</h5>}

        {/* 输入框操作 */}
        <div>
          {/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
          {/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
          <input type="text" value={this.state.text} onChange={this.textChange}/>
          <button onClick={this.addGood}>添加</button>
        </div>

        {/* 列表渲染 */}
        <ul>
          {this.state.goods.map(good => (
            <li key={good.id}>
              {good.name}
            </li>
          ))}
        </ul>

      </div>
    )
  }
}
// src/components/CartSample.js
import React from 'react';

export class CartSample extends React.Component{
  
  // 状态state初始化一般放在构造器中
  constructor (props) {
    super(props);
    this.state = {
      goods: [
        {id: 1, name: 'web'},
        {id: 2, name: 'ios'},
        {id: 3, name: 'pc'},
        {id: 4, name: 'mobile'},
      ],
      // 新增一个状态用于存储输入框的值
      text: '',
      // 用于保存购物车列表数据
      cart: []
    }

  // 这里建议写成箭头函数,因为这里的方法在调用时会影响this指向
  // 也就是textChange方法如果不用箭头函数表示,那么在调用时this默认指向调用者,也就是input,这样就拿不到组件状态state中的数据了
  // 如果不写成箭头函数,写成 textChange () {},那么在组件state中可以使用bind等方法重新设置函数的this指向
  textChange = (evt) => {
    this.setState({text: evt.target.value})
  }

  // 添加至商品列表
  addGood = () => {
    this.setState(prevState => {
      // 返回一个全新的要更新的数组,而不是在原数组上操作数据
      return {
        goods: [...this.state.goods, {id: prevState.goods.length + 1, name: prevState.text}]
      }
    })
  }

  // 添加至购物车
  addCart = good => {
    // 创建新购物车
    const newCart = [...this.state.cart]
    // 查找当前选中项是否已存在在购物车
    const idx = newCart.findIndex(c => c.id === good.id)
    const item = newCart[idx]
    if(item) {
      // 当前选中项已存在在购物车,购物车中的当前项数量+1
      newCart.splice(idx, 1, {...item, count: item.count + 1})
    }else {
      // 购物车新增一项
      newCart.push({...good, count: 1})
    }
    this.setState({cart: newCart})
  }

  // 购物车自减
  minus = good => {
    const newCart = [...this.state.cart]
    const idx = newCart.findIndex(c => c.id === good.id)
    const item = newCart[idx]
    newCart.splice(idx, 1, {...item, count: item.count - 1})
    this.setState({cart: newCart})
  }
  
  // 购物车自增
  add = good => {
    const newCart = [...this.state.cart]
    const idx = newCart.findIndex(c => c.id === good.id)
    const item = newCart[idx]
    newCart.splice(idx, 1, {...item, count: item.count + 1})
    this.setState({cart: newCart})
  }

  render(){
    // 判断父组件是否传入title属性
    let title = this.props.title ? <h5>{this.props.title}</h5> : null
    return (
      <div>
        {/* 条件渲染 */}

        {/* 写法1 */}
        {title}

        {/* 写法2 当this.props.title为空就不再继续执行 */}
        {this.props.title && <h5>{this.props.title}</h5>}

        {/* 输入框操作 */}
        <div>
          {/* 这里注意赋值给绑定事件的方法一定不能写成this.textChange(),这样就表示立即调用了,其实这里需要的是个方法体 */}
          {/* react没有数据双向绑定的概念,因此一个输入框必须要设置好2点,值得绑定和值变化时的处理方法 */}
          <input type="text" value={this.state.text} onChange={this.textChange}/>
          <button onClick={this.addGood}>添加</button>
        </div>

        {/* 列表渲染 */}
        <ul>
          {this.state.goods.map(good => (
            <li key={good.id}>
              {good.name}
              {/* 事件绑定:不需要传参时,像课程列表的添加事件,只需要传一个函数体就可以了 */}
              {/* 事件绑定:需要传参时,将绑定事件写成一个箭头函数,在函数中执行绑定的方法并传参 */}
              <button onClick={() => this.addCart(good)}>添加购物车</button>
            </li>
          ))}
        </ul>

        {/* 购物车 */}
        {/* 和Vue不同的是,react中子组件尽可能的只做数据展示,不做数据管理及事件操作,有需要事件操作的通知父组件进行操作 */}
        {/* 子组件需要执行的方法函数,在父组件中以属性的形式传过去 */}
        <Cart data={this.state.cart} minus={this.minus} add={this.add}></Cart>
      </div>
    )
  }
}

// 购物车组件
function Cart ({data, minus, add}) {
  return (
    <div>
      <table>
        <tbody>
            {data.map(d => (
              <tr key={d.id}>
                <td>{d.name}</td>
                <td>
                  <button onClick={() => minus(d)}>-</button>
                  {d.count}
                  <button onClick={() => add(d)}>+</button>
                </td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  )
}
上一篇 下一篇

猜你喜欢

热点阅读