学习react (未完成)

118 阅读8分钟

生命周期

  • getDefaultProps,通过这个方法初始化props属性,props来自于父组件,以及其他组件

  • getInitialState,初始化当前组件的状态,这个状态会贯穿整个项目,数据变化都取决于setState

  • componentWillMount,在组件加载和初始化之前调用

  • render ,react中最重要的方法,react元素的渲染都取决于render

  • componentDidMount,react DOM加载完会调用这个方法

  • componentWillReceiveProps,来自父组件属性的传递调用的方法

  • shouldComponentUpdate,组件的更新,只要调用setState就会调用这个生命周期钩子

  • componentWillUpdate,组件更新之前调用的方法

  • componentDidUpdate ,组件更新之后调用的方法

  • componentWillUnmount,组件销毁调用的方法

创建项目命令

react.docschina.org/

npm install -g create-react-app
 create-react-app my-app      

reate-redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

如果想要了解详情可以查看官方网www.redux.org.cn/ 下面截图测试代码文件格式如下

image.png

Provider

index.js

import React from 'react'
import ReactDOM from 'react-dom'
// ## Provider 提供者配合 connect 链接器
import { Provider } from 'react-redux'
// 跟vue类似引入store【初始状态和方法】
import store from './store'
import App from './App'
// Provider 包裹APP
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

APP.js

import React from 'react'
import { connect } from 'react-redux'
import { decrementActionCreator, incrementActionCreator,resetCreator } from './actions/counter'

function App(props) {
  return (
    <div>
      { props.count }

      <button onClick={props.plus}>加数量</button>
      <button onClick={props.minus}>减数量</button>
      <button onClick={props.reset}>我要将它重置为0</button>
    </div>
  )
}

/**
 * 将 store 中 state 的数据映射到组件的属性中
 * mapStateToProps 是一个函数,会传递 store 中的 state 作为参数。
 * 返回一个普通对象,该对象会被合并到组件的 props 中
 * @param {*} state 
 * @returns 
 */
const mapStateToProps = state => ({
  count: state.counter.count,
  num: state.counter.num,
})

/**
 * 将更新状态数据的动作映射到组件的属性中
 * mapDispatchToProps 是一个函数,会传递 dispatch 作为参数。
 * 返回一个对象,返回对象中的各字段应该是函数(方法),返回
 * 对象中的各字段也会被合并到组件的 props 中。
 */
const mapDispatchToProps = dispatch => ({
  plus: () => dispatch(incrementActionCreator()),
  minus: () => dispatch(decrementActionCreator()),
  reset: () => dispatch(resetCreator()),
})

/* 调用 connect() 函数,在 React 组件中,连接 redux 的 store */
const hoc = connect(mapStateToProps, mapDispatchToProps)

export default hoc(App)

actions/counter.js

// export const INCREMENT = 'increment'
// export const DECREMENT = 'decrement
// import { DECREMENT, INCREMENT } from "./constants"
/**
 * action creator,action创建函数,主要用于生成 action 对象
 * @returns 
 */
export const incrementActionCreator = () => ({
  // type: INCREMENT,
  type: "increment",
  payload: {
    num: 5
  },
})

export const decrementActionCreator = () => ({
  type: "decrement",
  payload: {
    num: 2
  },
})

export const resetCreator = () => ({
  type: "RESETACTION",
  payload: {
    num: 0
  },
})

store/index.js

import { createStore } from 'redux'
import rootReducer from '../reducers'

// 创建 Store 对象
export default createStore(rootReducer)

reducers/counter.js

import { DECREMENT, INCREMENT } from "../actions/constants"

/**
 * state 初始值
 */
const initialState = {
  count: 32, // 计数数量的状态数据
  num:4
}

/**
 * reducer 纯函数,用于实现状态的同步更新,返回更新后的新状态数据
 * @param {*} state 更新前的状态数据 
 * @param {*} action 描述发生了什么,有 type 与 payload 属性
 * @returns 
 */
const reducer = (state = initialState, { type, payload }) => {
  switch (type) {
    case INCREMENT: // 加数量
      const copy = { ...state } // 复制 state 值
      copy.count += payload.num // 对复制后的副本进行更新
      return copy // 返回更新后的新状态数据
    case DECREMENT: // 减数量
      const cp = { ...state } // 复制 state 值
      cp.count -= payload.num // 对复制后的副本进行更新
      return cp // 返回更新后的新状态数据
      case "RESETACTION": // 减数量
      const test1 = { ...state } // 复制 state 值
      test1.count = payload.num // 对复制后的副本进行更新
      return test1 // 返回更新后的新状态数据
    default:
      return state
  }
}


export default reducer

reducers/index.js

import { combineReducers } from 'redux'
import counter from './counter'

// 将多个独立的 reducer 最终合并为一个根 reducer
export default combineReducers({
  counter
})

CSS 框架 Bulma

在项目入口index.js

import 'bulma/css/bulma.min.css'

解决 react is defined but never used no-unused-vars

根目录创建.eslintrc.json

{
    "plugins": ["react"],
    "extends": [
        "plugin:react/recommended"
    ]
}

render

类组件渲染 App.JSX

import React, { Component } from 'react'

import './App.css';
import withFooter from './utils/width-footer'

class App extends Component {
  render(){
    return (
      <h1>Hello, world! </h1>
    )
  }
}

// export default withFooter(App)
export default App

创建版本包裹组件 将App 当参数传递给withFooter

import withFooter from './utils/width-footer'
export default withFooter(App)

Component

zh-hans.reactjs.org/docs/react-…
如需定义 class 组件,需要继承 React.ComponentReact.Component 的子类中有个必须定义的 render() 函数。

使用 Component 创建width-footer.js

import React from 'react'
/**
 * HOC
 * @param {*} InComponent 
 * @returns 
 */
const widthfooter = InComponent =>{
    console.log(InComponent,'接收App');
    class OutComponent extends React.Component{
        render(){
            return (
                <>
                 <InComponent />
                 <div>版权声明&copy; 1</div>
                </>
            )
        }
    }
    return OutComponent
}
export default widthfooter

image.png

ES6 Array.fill( )

来填充一个全是空白对象的数组

  state = {
    todos: Array(6).fill(null).map((v, i) => ({
      id: i + 1,
      title: '待办事项' + (i + 1),
      completed: true
    }))
    // todos: []
  }

createContext

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

import { Provider } from './utils/TodoContext' 引入共享工具 包裹组件最外层 被抱被包裹的组件 被包裹的组件同样需要引入共享工具TodoContext.js
使用的地方 static contextType = TodoContext
console.log(this.context,"this.context"); // name:测试

<Provider value={{name:'测试'}}/>

import React from 'react'

// 创建 Context 对象
const TodoContext = React.createContext()
// 解构生产与消费组件
const {Provider, Consumer} = TodoContext

// 导出
export {
  TodoContext,
  Provider,
  Consumer,
}

在需要使用的页面 先引入/utils/TodoContext

prop-types

prop-types检测数据类型 创建头部header

import TodoHeader from './components/TodoHeader'

  class App extends Component {
  state = {
    todos: Array(6).fill(null).map((v, i) => ({
      id: i + 1,
      title: '待办事项' + (i + 1),
      completed: true
    }))
    // todos: []
  }
  render(){
    return (
      <Provider  value={{name:'测试'}}>
        <TodoHeader title="待办事项列表" subtitle="ToDoList..."></TodoHeader>
      </Provider>
    )
  }
}

TodoHeader.jsx

import React, { Component } from 'react'
// 定义类型
import PropTypes from 'prop-types'
export default class TodoHeader extends Component {
  // 设置运行时类型检测规则
  static propTypes = {
    title: PropTypes.string.isRequired,
    subtitle: PropTypes.string,
    children: PropTypes.string,
  }
    // 设置组件接收属性的默认值
    static defaultProps = {
        title: '默认的主标题',
        subtitle: '默认的副标题',
        children:""
}
    render (){
        const { title, subtitle, children } = this.props
        return (
            <section className="hero is-primary">
            <div className="hero-body">
              {
                children
                  ?
                  children
                  :
                  (
                    <>
                      <p className="title">
                        { title }
                      </p>
                      <p className="subtitle">
                        { subtitle }
                      </p>
                    </>
                  )
              }
            </div>
          </section>
        )
    }
}

setState

react如果需要修改state的值需要使用setState

  state = {
    type: 'all' // 选项卡状态
  }
    // 点击,设置当前选项卡状态
  handleChange(type, event) {
    event.preventDefault()
    this.setState({
      type
    })
  }

react渲染模式模式显示在页面上

render()

     {
            (
              <div>
                {
                  this.state.todos.map(item => (
                    <div key={item.id}>
                      {item.title}
                    </div>
                  ))
                }
              </div>
            )
        }

组件通讯(函数组件 类组件)

父传子

类组件

<TodoHeader title="待办事项列表" subtitle="ToDoList..."></TodoHeader>

const { title, subtitle, children } = this.props 子组件

// 父组件
<Hello name="jack" age={19} />
// 子组件
class Hello extends React.Component {
    render() {
        return (
            <div>接收到的数据:{this.props.age}</div>
        )
    }
}

函数组件

// 父组件
<Hello name="jack" age={19} />
// 子组件
function Hello(props) {
    console.log(props)
    return (
        <div>接收到数据:{props.name}</div>
    )
}

子传父

// 父组件提供函数并且传递给字符串
class Parent extends React.Component {
    getChildMsg = (msg) => {
        console.log('接收到子组件数据', msg)
    }
    render() {
        return (
            <div>
                子组件:<Child getMsg={this.getChildMsg} />
            </div>
        )
    }
}

// 子组件接收函数并且调用
class Child extends React.Component {
    state = { childMsg: 'React' }
    handleClick = () => {
        this.props.getMsg(this.state.childMsg)
    }
    return (
        <button onClick={this.handleClick}>点我,给父组件传递数据</button>
    )
}

跨级组件

跨级组件:所谓跨级组件通信,就是父组件向子组件的子组件通信,向更深层的子组件通信。跨级组件通信可以采用下面两种方式:

中间组件层层传递 props 使用 context 对象 对于第一种方式,如果父组件结构较深,那么中间的每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是这些中间组件自己所需要的。不过这种方式也是可行的,当组件层次在三层以内可以采用这种方式,当组件嵌套过深时,采用这种方式就需要斟酌了。

使用 context 是另一种可行的方式,context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。 使用 context 也很简单,需要满足两个条件:

上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象 子组件要声明自己需要使用 context

进入正题--------------- 先来看下不跨层级传递参数:

import React, { Component } from 'react'


class Grandson extends Component {
    render() {
        return (
            <header>
            	// 孙子组件接收来自子组件的参数
                { this.props.title }
            </header>
        )
    }
}

class Child extends Component {
    render() {
        return (
        	{/* 子组件先拿到父组件的参数 
        	然后 传递给孙子组件 */}
            <Grandson title={ this.props.title } />
        )
    }
}
class App5 extends Component {
    render() {
        return (
        	{/* 父组件的参数 title 
        	通过Child组件 传递给子组件*/}
            <Child title='标题' />
        )
    }
}

export default App5


再来看看context跨层级传递参数:

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


class Grandson extends Component {
	// 3, 设置参数类型 
    static contextTypes = {
        title: PropTypes.string
    }
    render() {
        return (
            <header>
            	{/* 这里说明,携带参数的,已经不是props了,
            	而是context */}
                { this.context.title }
            </header>
        )
    }
}

class Child extends Component {
    render() {
        return (
            <Grandson />
        )
    }
}
class App5 extends Component {
	// 2,设置参数类型 写法固定
    static childContextTypes = {
        title: PropTypes.string
    }
    // 1,返回一个对象,里面是携带的参数。固定写法
    getChildContext() {
        return {
            title: '标题'
        }
    }
    render() {
        return (
            <Child />
        )
    }
}

export default App5


createRef

import React, { Component, createRef } from 'react'

    // 创建 Ref 对象
    inputRef = createRef()
    // 文本框自动获得焦点
    // 字符串 ref 已废弃,不建议再使用
    // this.refs.inputRef.focus()
    // console.log(this.inputRef)
    this.inputRef.current.focus()

Route

v5.reactrouter.com/web/api/Rou… 路由 在项目入口index.js 引入 react-router-dom 并且包裹app

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App'

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

在App.jsx中同样引入

import React from 'react'
import { Route, Link, Redirect, Switch } from 'react-router-dom'
import TabBar from './components/TabBar'
import Category from './views/Category'
import Home from './views/Home'
import Mine from './views/Mine'

export default function App() {
  return (
    <div>
      App

      <ul>
        <li><Link to="/home">首页</Link></li>
        <li><Link to="/category">分类</Link></li>
        <li><Link to="/mine">我的</Link></li>
      </ul>

      <Switch>
        <Route path="/category" component={Category} />
        <Route path="/home">
          <Home />
          <TabBar />
        </Route>
        <Route path="/mine" render={() => (
          <>
            <Mine />
            <TabBar />
          </>
        )} />

        <Redirect to="/home" />
      </Switch>
    </div>
  )
}

withRouter

import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'

class SubCategory extends Component {
  render() {
    console.log('sub:', this.props)
    return (
      <div>
        子分类页面 { this.props.location.search || JSON.stringify(this.props.match.params) }
        <button onClick={() => this.props.history.push('/home')}>首页</button>
      </div>
    )
  }
}

// 如果在组件中需要使用到路由的 history、location、match 对象,但
// 组件属性中这些对象不存在,则可以使用 withRouter() 这个 HOC 来为
// 包裹组件注入(增强)这几个对象的使用
// export default SubCategory
export default withRouter(SubCategory)

import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import SubCategory from './SubCategory'

export default class Category extends Component {
  render() {
    return (
      <div>
        分类页面-主分类
        <div><Link to="/category/sub?id=11111111">分类1</Link></div>
        <div><Link to="/category/sub/2222222">分类2</Link></div>

        <Route path="/category/sub/:id?">
          <SubCategory />
        </Route>
      </div>
    )
  }
}