一、React基础

起步

1、安装官方脚手架: npm install -g create-react-app

2、创建项目:create-react-app react-project-name

3、启动项目:npm start

4、暴露配置项:npm run eject

入口文件:index.js

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

babel-loader 最终会把jsx转换成js能识别的内容

JSX:是一种JavaScript的语法扩展,其格式比较像模板语言,但事实上完全是在JavaScript内部实现的

JSX实质就是 React.createElement 的调用,最终的结果是React元素(JavaScript对象)

const jsx = <div>hello react</div>
ReactDOM.render(jsx, document.getElementById('root'));

组件

组件是抽象的独立功能模块,react应用程序由组件构建而成

组件的两种表现形式:class组件  function组件

class组件通常拥有状态(state)和生命周期(lifeCycle),继承于Component,实现render方法

import React, { Component } from 'react'

export default class Home extends Component {
    render() {
        return (
            <div>
                我是home页面
            </div>
        )
    }
}

function组件

函数组件通常无状态,仅关注内容展示,返回渲染结果即可。

---从React16.8开始引入了hooks,函数组件也能够拥有状态。

写一个定时器显示当前组件

import React, { Component } from 'react'

export default class Home extends Component {
    constructor(props){
        super(props)
        this.state = {
            date: new Date()
        }
    }
    // 组件挂载后---生命周期函数
    componentDidMount(){
        this.timerId = setInterval(()=>{
            this.setState({
                date: new Date()
            })
        }, 1000)
    }
    // 组件卸载之前
    componentWillUnmount(){
        clearInterval(this.timerId)
    }
    render() {
        const str = '我是home页面'
        const {date} = this.state
        return (
            <div>
                <h1>{str}</h1>
                <p>{date.toLocaleTimeString()}</p>
            </div>
        )
    }
}

setState方法

setCounter(){  // 这样写找不到this
    this.setState({
        counter: this.state.counter + 1
    })
}

// 解决方法一
constructor(props){
    super(props)
    this.state = {
        date: new Date(),
        counter: 0
    }
    this.setCounter = this.setCounter.bind(this)
}
setCounter(){
    this.setState({
        counter: this.state.counter + 1
    })
}

// 解决方法二
setCounter = () => {
    this.setState({
        counter: this.state.counter + 1
    })
}

setState为异步,如何将其实现同步

setCounter = () => {
    this.setState({
        counter: this.state.counter + 1
    })
    console.log(this.state.counter) // 这里打印0,页面显示1
}

// 打印0时,页面显示2,并且每次只加2
setCounter = () => {
    this.setState({
        counter: this.state.counter + 1
    })
    this.setState({
        counter: this.state.counter + 2
    })
    console.log(this.state.counter) 
}

// 实现同步
// 方式一
setCounter = () => {
    this.setState(nextState => {
        return {
            counter: nextState.counter + 1
        }
    })
    this.setState(nextState => {
        return {
            counter: nextState.counter + 2
        }
    })
}

// 方式二
setCounter = () => {
    setTimeout(() => {
        this.setState({
            counter: this.state.counter + 1
        })
        this.setState({
            counter: this.state.counter + 2
        })
        console.log(this.state.counter)
    }, 0)
}

// 方式三:原生事件上去绑定
componentDidMount(){
    document.getElementsByTagName('button')[0].addEventListener('click', ()=>{
        this.setState({
            counter: this.state.counter + 1
        })
        this.setState({
            counter: this.state.counter + 2
        })
    })
}

 总结:setState只有在合成事件和钩子函数中是异步的,在原生事件和setTimeout、setInterval中都是同步的

function组件

function组件是没有状态和生命周期的

如果一定要使用state,可以进行引入 useState、useEffect

下列代码将上面的class组件转成funciton组件

import React, {useState, useEffect} from 'react'

export default function User() {
    const [date, setDate] = useState(new Date())
    useEffect(() => {
        const timerId = setInterval(() => {
            setDate(new Date())
        }, 1000)
        return () => clearInterval(timerId)
    })
    return (
        <div>
            <h1>我是user页面</h1>
            <p>{date.toLocaleTimeString()}</p>
        </div>
    )
}

事件处理

onclick事件和onchange事件

import React, { Component } from 'react'

export default class Search extends Component {
    constructor(props){
        super(props)
        this.state = {
            name: '12'
        }
    }
    handler = () => {
        console.log('handler');
    }
    change = (event) => {
        let value = event.target.value
        this.setState({
            name: value
        })
        console.log('change', this.state.name);
    }
    render() {
        const {name} = this.state
        console.log(this);return (
            <div>
                <h1>我是Search页面</h1>
                <button onClick={this.handler}>click</button>
                <input onChange={this.change} value={name}/>
            </div>
        )
    }
}

事件回调函数注意绑定this指向,常见三种方法:

1、构造函数中绑定并覆盖:this.change = this.change.bind(this)

2、方法定义为箭头函数:change = ()=>{}

3、事件中定义为箭头函数:onChange = { () => this.change() } 

--- react里遵循单项数据流,没有双向绑定,输入框要设置value和onChange,成为受控组件。

组件通信

props属性传递

// App.js中
const store = {
  userInfo: {
    userName: 'Dylan'
  }
}
function tellme(msg){
  console.log('tellme:', msg);
}
function App() {
  return (
    <div className="App">
      <Search store={store} tellme={tellme} />
    </div>
  );
}

export default App;

// Search.js中
handler = () => {
    console.log('handler');
    const {tellme} = this.props
    tellme('im dylanLv')
}
render() {
    const {name} = this.state
    console.log(this);
    const {userInfo} = this.props.store
    return (
        <div>
            <h1>我是Search页面,{userInfo.userName}</h1>
            <button onClick={this.handler}>click</button>
            <input onChange={this.change} value={name}/>
        </div>
    )
}

如果父组件传递的是函数,则可以把子组件信息传入父组件,这个常称为状态提示 

react16的生命周期

import React, {Component} from 'react'

export default class LifeCycle extends Component {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
    console.log('constructor', this.state.counter);
  }
  // 挂载之前
  UNSAFE_componentWillMount() {
    console.log('componentWillMount', this.state.counter);
  }
  // 挂载之后
  componentDidMount() {
    console.log('componentDidMount', this.state.counter);
  }
  // 更新之前
  UNSAFE_componentWillUpdate() {
    console.log('componentWillUpdate', this.state.counter);
  }
  // 更新之后
  componentDidUpdate() {
    console.log('componentDidUpdate', this.state.counter);
  }
  // 卸载之前
  componentWillUnmount() {
    console.log('componentWillUnmount', this.state.counter);
  }
  // 是否更新--执行render--优化使用
  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate', this.state.counter, nextState.counter, nextProps);
    return this.state.counter != 5
  }
  setCounter = () => {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  render() {
    console.log('render');
    const {counter} = this.state
    return (
      <div>
        <h1>我是一个lifeCycle, Hylan</h1>
        <p>{counter}</p>
        <button onClick={this.setCounter}>click</button>
        {!!(counter % 2) && <Foo/>}
      </div>
    )
  }
}

class Foo extends Component {
  componentWillUnmount() {
    console.log('componentWillUnmount   foo');
  }
  render() {
    return <div>我是FOO组件</div>
  }
}

V17可能会废弃的三个生命周期函数用 getDerivedStateFromProps 替代,目前使用的话加上 UNSAFE_

  • componentWillMount
  • componentWillReceiveProps
  • conponentWillUpdate

引入两个新的生命周期

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate
static getDerivedStateFromProps(props, state){
  // getDerivedStateFromProps 会在调用render方法之前调用
  // 并且在初始挂载以及后续更新时都会被调用
  // 它应返回一个对象来更新 state ,如果返回 null 则不更新任何内容
  const { counter } = state
  console.log('getDerivedStateFromProps', counter)
  return counter < 8 ? null : { counter: 0 }
}

// 上一次(更新前)的影像
// 返回值会作为 componentDidUpdate 的第三个参数
getSnapshotBeforeUpdate(prevProps, prevState){
  const { counter } = prevState
  console.log('getSnapshotBeforeUpdate', counter)
  return null
}
// 更新之后
componentDidUpdate(prevProps, prevState, snapshot) {
  console.log('componentDidUpdate', this.state.counter);
}

使用Context 上下文传值---可以跨层级传值

// App.js
import React from 'react';
import './App.css';
import Home from './pages/Home';

const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer

const store = {
  home: {},
  user: {
    name: 'Dylan'
  }
}

function App() {
  return (
    <div className="App">
      <Provider value={store}>
        <Consumer>
          {ctx => <Home {...ctx}/>}
        </Consumer>
      </Provider>

    </div>
  );
}

export default App;

// Home.js
import React, { Component } from 'react'

export default class Home extends Component {
    render() {
        // home:{
        //     home: {}
        //     user: {name: "Dylan"}
        // }
        console.log("home", this.props);
        return (
            <div>
                home
            </div>
        )
    }
}

一般使用时会将Context暴露在外部,使用者将其引用就行了

 AppContext.js 文件

import React from 'react';

const Context = React.createContext()
export const Provider = Context.Provider
export const Consumer = Context.Consumer

App.js 文件

import React from 'react';
import './App.css';
import Home from './pages/Home';
import {Provider, Consumer} from './AppContext'
const store = {
  home: {},
  user: {
    name: 'Dylan'
  }
}
function App() {
  return (
    <div className="App">
      <Provider value={store}>
        <Consumer>
          {ctx => <Home {...ctx}/>}
        </Consumer>
      </Provider>

    </div>
  );
}
export default App;

跨层级传值

App.js 文件

const store = {
  home: {},
  user: {
    name: 'Dylan'
  }
}
function App() {
  return (
    <div className="App">
      <Provider value={store}>
        <Home />
      </Provider>

    </div>
  );
}

Home.js文件

import React, { Component } from 'react'
import {Consumer} from '../AppContext'

export default class Home extends Component {
    render() {
        console.log("home", this.props);
        return <Consumer>
            {ctx => <HomeHandle {...ctx}/>}
        </Consumer>
    }
}

function HomeHandle(props){
    console.log('HomeHandle', props);
    return <div>
        <h1>HomeHandle</h1>
    </div>
}

组件复合---Composition

TabBar.js 文件

import React, {Component} from 'react'

export default class TabBar extends Component {
  render() {
    return <div className="tabBar">TabBar</div>
  }
}

Layout.js 文件

import React, {Component} from 'react'
import TabBar from '../components/TabBar'

export default class Layout extends Component {
  render() {
    const {children, showTabBar = true} = this.props
    return (
      <div>
        {children}
        {showTabBar && <TabBar />}
      </div>
    )
  }
}

Home.js  文件 ,使用Layout模板

import React, {Component} from 'react'
import {Consumer} from '../AppContext'
import Layout from './Layout';

export default class Home extends Component {
  render() {
    return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
  }
}

function HomeHandle(props) {
  console.log('HomeHandle', props);
  return <Layout showTabBar={true}>
    <div>
      <h1>Home</h1>
    </div>
  </Layout>
}

上述类似  vue  中的匿名插槽

下面代码展示具名插槽

Home.js 文件

import React, {Component} from 'react'
import {Consumer} from '../AppContext'
import Layout from './Layout';

export default class Home extends Component {
  render() {
    return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
  }
}

function HomeHandle(props) {
  console.log('HomeHandle', props);
  return <Layout>
    {{
        btn: <button>按钮</button>,
        content: "我是内容"
    }}
    
  </Layout>
}

Layout.js  文件

import React, {Component} from 'react'
import TabBar from '../components/TabBar'

export default class Layout extends Component {
    componentDidMount(){
        const { title='商场' } = this.props
        document.title = title
    }
  render() {
      const {children, showTabBar} = this.props
      console.log('props:',this.props);
    return (
      <div>
        {children.btn}
        {children.content}
        {showTabBar && <TabBar />}
      </div>
    )
  }
}

如何让layout自行判断传进来的组件是具名还是匿名

Layout.js 文件

render() {
    const {children, showTabBar = true} = this.props
    const a = []
    // 如果有这个值($$typeop) , 说明是匿名的
    if(children.$$typeof){
    a.push(children)
    }else{
        for(let item in children){
            a.push(children[item])
        }
    }
return (
    <div>
    {
        a.map((item, index) => {
            return <div key={`child${index}`}>{item}</div>
        })
    }
    {showTabBar && <TabBar />}
    </div>
)

高阶组件

将Home.js 进行修改

import React, {Component} from 'react'
import {Consumer} from '../AppContext'
import Layout from './Layout';

const consumerHandler = Cmp => props => {
  return <Consumer>
    {ctx => <Cmp {...props} {...ctx}/>}
  </Consumer>
}

function HomeHandle(props) {
  console.log('HomeHandle', props);
  return <Layout>
    <div>
      <h1>home</h1>
    </div>
  </Layout>
}

export default consumerHandler(HomeHandle)

可以将其放入 AppContext.js 文件(公共文件)

import React from 'react';

const Context = React.createContext()
export const Provider = Context.Provider
export const Consumer = Context.Consumer

export const consumerHandler = Cmp => props => {
  return <Consumer>
    {ctx => <Cmp {...props} {...ctx}/>}
  </Consumer>
}

高阶组件---HOC

为了提高组件复用性,可测试性,就要保证组件功能单一性;但是若要满足复杂需求就要扩展功能单一的组件,在React里就有了HOC(Higher-Order Components)的概念。

定义:高阶组件就是一个工厂函数,它接收一个组件并返回另一个组件。

改写 App.js,展示高阶组件的用法

function Child(props){
  return <div>child</div>
}

const foo = Cmp => props => {
  return <div style={{border: '1px solid black'}}>
    <Cmp {...props}/>
  </div>
}


function App() {
  const Foo = foo(Child)
  return (
    <div className="App">
      <Child />
      <Foo />
      {/* <Home /> */}
      {/* <User /> */}
    </div>
  );
}
export default App;

高阶组件实际上就是,你给我一个组件,我把这个组件封装一下,再返回给你一个封装后的组件

高阶组件可以链式调用

function Child(props){
  return <div>child</div>
}

const foo = Cmp => props => {
  return <div style={{border: '1px solid black'}}>
    <Cmp {...props}/>
  </div>
}

const foo2 = Cmp => props => {
  return <div style={{border: '1px solid red', padding: '10px'}}>
    <Cmp {...props}/>
  </div>
}

function App() {
  const Foo = foo2(foo(Child))
  return (
    <div className="App">
      <Child />
      <Foo />
    </div>
  );
}
export default App;

装饰器写法

高阶组件本身是对装饰器模式的应用,自然可以利用ES7中出现的装饰器语法来更优雅的书写代码。

CRA项目中默认不支持js代码使用装饰器语法,可修改后缀名为 tsx 则可以直接支持

Hooks

Hook是React16.8的一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hooks的特点:

  • 是你在无需修改组件结构的情况下复用状态逻辑
  • 可将组件中相互关联的部分拆分成更小的函数,复杂组件将变得更容易理解
  • 更简洁、更易理解的代码

hook中使用子组件,代码如下:

import React, {useState, useEffect} from 'react'

export default function HooksPage() {
  const [date, setDate] = useState(new Date())
  const [fruits, setFruits] = useState(['apple', 'banana'])
  useEffect(() => {
    const timerId = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => clearInterval(timerId)
  })
  return (
    <div>
      hooks
      <p>{date.toLocaleTimeString()}</p>
            {/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
      <FruitList fruits={fruits} setFruits={setFruits}/>
    </div>
  )
}

function FruitList({fruits, setFruits}) {
    // 点击后删除当前项
    const delCur = index => {
        const tem = [...fruits]
        tem.splice(index, 1)
        setFruits(tem)
    }
  return <div>
    <ul>
      {
         fruits.map((item, index) => {
            return <li key={`fruit${index}`} onClick={()=>delCur(index)}>{item}</li>
         })
      }
    </ul>
  </div>
}

添加一个新增 fruits 操作

export default function HooksPage() {
  const [date, setDate] = useState(new Date())
  const [fruits, setFruits] = useState(['apple', 'banana'])
  useEffect(() => {
    const timerId = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => clearInterval(timerId)
    })
  return (
    <div>
      hooks
      <p>{date.toLocaleTimeString()}</p>
    {/* 传入addFruit函数 */}
     <AddFruit addFruit={item=>setFruits([...fruits, item])} />
    {/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
      <FruitList fruits={fruits} setFruits={setFruits}/>
    </div>
  )
}

function AddFruit({addFruit}) {
  const [name, setName] = useState("")
  return <div>
   <input value={name} onChange={e => setName(e.target.value)} />
   {/* 接收addFruit函数,点击按钮进行触发 */}
    <button onClick={()=>addFruit(name)}>点击增加</button>
  </div>
}

如果在 useEffect 中加入  console.log('useEffect'),可以发现不管是 setDate执行,还是点击新增按钮后的 setFruits 执行,console.log都会执行。

但实际上当前功能只需要 date的定时器不断执行而已,可以修改代码进行优化

useEffect(() => {
  console.log('useEffect');
  const timerId = setInterval(() => {
    setDate(new Date())
  }, 1000)
  return () => clearInterval(timerId)
  // useEffect的第二个参数,加入后就只对 date 生效了
}, [date])

接下来 点击 新增按钮 ,就不会打印 useEffect 了

副作用钩子

useEffect给函数组件增加了执行副作用操作的能力。

副作用(Side Effect)是指一个function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log ,所以ajax操作,修改dom都算做副作用。

  • 异步数据获取,更新HooksTest.js
useEffect(() => {
  setTimeout(()=>{
    setFruits(['香蕉','西瓜'])
  }, 1000)
})

测试就会发现副作用操作会被频繁调用

  • 设置依赖
// 设置空数组意为没有依赖,则副作用操作仅执行一次
useEffect(()=>{...}, [])
  • 清除工作:有一些副作用是需要清除的,清除工作非常重要,可以防止引起内存泄漏
useEffect(() => {
  const timerId = setInterval(() => {
    console.log('mgs');
  }, 1000)
  return () => clearInterval(timerId)
}, [])

组件卸载后回执行返回的清理函数

useReducer

useReducer是useState的可选项,常用于组件有复杂状态逻辑时

将上面的代码改成使用 useReducer

Fruit.js 文件

import React, {useState, useEffect} from 'react'

export function AddFruit({addFruit}) {
  const [name,
    setName] = useState("")
  return <div>
    <input value={name} onChange={e => setName(e.target.value)}/> 
    <button onClick={() => addFruit(name)}>点击增加</button>
  </div>
}

// FruitList 组件需要传入 fruits 和 setFruits
export function FruitList({fruits, setFruits}) {
  // 点击后删除当前项
  const delCur = index => {
    const tem = [...fruits]
    tem.splice(index, 1)
    setFruits(tem)
  }
  return <div>
    <ul>
      {
        fruits.map((item, index) => {
          return <li key={`fruit${index}`} onClick={() => delCur(index)}>{item}</li>
        })
      }
    </ul>
  </div>
}

HookReducer.js 文件

import React, { useEffect, useReducer } from 'react'
import { FruitList, AddFruit } from '../components/Fruit'

function fruitsReducer(state, action){
  switch(action.type){
    case 'init':
    case 'replace':
      return action.paylod
    default:
      return state
  }
}

export default function HooksReducer() {
  const [fruits, dispatch] = useReducer(fruitsReducer, [])
  useEffect(()=>{
    setTimeout(()=>{
      dispatch({type: 'init', paylod: ['apple', 'banana']})
    }, 1000)
    return ()=>{
      // clearup
    }
  }, [])
  return (
    <div>
      HooksReducer
      <FruitList fruits={fruits} setFruits={(newFruitList)=>dispatch({type: 'replace', paylod: newFruitList})}/>
    </div>
  )
}

useContext

useContext 用于 快速在函数组件中导入上下文。---之前是用Consumer做的

import React, { useContext } from 'react'

const Context = React.createContext()
const Provider = Context.Provider

export default function HooksContext() {
  const store = {
    user: {
      name: 'Dylan'
    }
  }
  return (
    <div>
      <Provider value={store}>
        <ContextChild></ContextChild>
      </Provider>
    </div>
  )
}

function ContextChild(props){
  console.log(useContext(Context));
  const {user} = useContext(Context)
  return <div>
    {user.name}
  </div>
}

 

 
原文地址:https://www.cnblogs.com/haishen/p/11672813.html