react 入門

react diff算法 -> fiber算法css

小貼士:小圖標庫 https://emojipedia.org/html

0、create-react-app腳手架建立項目

npx create-react-app test-react //經過npx 使用create-react-app腳手架建立項目test-react //npx 臨時安裝一個模塊
//此時若是執行 npm run eject ,則執行解包,把構建配置寫入package.json,及生成config和scripts文件夾等,且解包後不能再打包回去了。解包以前的package.json裏的scripts腳本命令也自動改變。 使用中根據須要選擇是否解包配置。
package.json文件dependencies中react-dom用於建立前端組件(app用react-native),react-scripts用於構建(底層封裝的webpack)前端

一、定義組件的方式:

① 函數方式:java

import React from 'react'
  import ReactDom from 'react-dom'
  //函數方式原理:
  // 一、初探: 將xml格式虛擬DOM直接賦值給一個變量
  // const app = <h1> welcome React 16.8 </h1>
  // 二、深刻:是否能夠靈活的動態改變虛擬dom的屬性或子元素內容呢? 
  //   2.1 定義成一個方法傳參進去,而後返回該虛擬dom.
  //       const render = (props)=> <h1>Welcome {props.title}</h1>
  //   2.2 而後,建立組件時傳入參數。
  //       const app = render({
  //           title:'React 16.8'
  //       })

  const App = (props)=>{
    return (
      <div>    {/* jsx 虛擬dom元素不要加引號*/}
         {/* 只要是jsx裏要插入js代碼,加一層花括號(包括註釋),兩種語法格式且可嵌套交替使用*/}
        <h1 title={props.title}>Welcome {props.title}!!!</h1>
      </div>
    )
  }
  
  //將虛擬dom組件渲染到網頁真正dom樹上
  ReactDom.render(
    <App title='ksfg' />,
    document.querySelector('#root')  //渲染到public/index.html
    // ,callback
  )

② 類繼承React.Component: 經常使用(由於其能夠構建狀態和管理、聲明週期設置等)python

import React, {Component} from 'react' 
  import { render} from 'react-dom'
  class App extends Component {
    render(){ //React.Component提供的方法
      <div>
        <h1>類組件</h1>
        <p>{this.props.title}</p>
      </div>
    }
  }


  //將虛擬dom組件渲染到網頁真正dom樹上
  render(
    <App title='類組件繼承自React.Component' />
    document.querySelector('#root')
  )

二、jsx 編譯原理:

① 建立虛擬dom樹對象react

const vdom = {
      tag: 'div',
      attrs:{
        className: ['active',''],
        id:'root'
      },
      children:[
        {
          tag:'',
          attrs:{},
          children:[
            {...}
          ]
        }
      ]
    }

② 調用 React.createElement(type,attrs={},[...children])方法 把vdom樹渲染到頁面;
React.createElement() 方法內部根據 children 遞歸調用該方法把全部dom元素渲染出來。webpack

三、jsx中添加類樣式的方式:

① 添加內聯樣式web

const color = { color:'#F00'}
    <p style={color}>添加樣式1</p>
    或 <p style={{color:'#F00'}}>添加樣式1</p> //JS語句寫入大括號內

② 添加外部引入css文件算法

import './index.css'
    <p className="color-red">添加樣式1</p>

③ 動態(是否)添加樣式 (npm i classnames -S

import classnames from 'classnames'
    <p className={classnames('a',{'b':true,'c':false})}>添加樣式1</p>  
    {/*添加了'a'和'b'樣式*/}

④ styled-components插件標籤方式添加樣式 (npm i styled-components -S

import styled from 'styled-components'
    const Title = style.h1`
      color:#F00
    `
    <Title>添加樣式1</Title>

⑤ styled-jsx 插件 jsx方式添加樣式

四、什麼時候不能使用函數式組件

① 有狀態 state 時;
② 要使用生命週期鉤子時;
③ 須要使用 ref 時(由於ref要掛到實例上,函數式組件不能建立實例,但你能夠在函數組件內部使用 ref 屬性,只要它指向一個 DOM 元素或 class 組件);
.....

五、受控組件的value指定爲prop,組件的輸入文本內容將不能直接編輯

在受控組件上指定 value 的 prop 能夠防止用戶更改輸入。若是指定了 value,但輸入仍可編輯,則多是意外地將value 設置爲 undefined 或 null。

六、正確地使用 State

① 不要直接修改 State ,不會從新渲染組件 ,而是應該使用 setState() 。構造函數是惟一能夠給 this.state 賦值的地方。
② State 的更新多是異步的,出於性能考慮,React 可能會把多個 setState() 調用合併成一個調用 。 由於 this.props 和 this.state 可能會異步更新,因此你不要依賴他們的值來更新下一個狀態 ,要解決這個問題,可讓 setState() 接收一個函數而不是一個對象。這個函數用上一個 state 做爲第一個參數,將這次更新被應用時的 props 作爲第二個參數 。
③ State 的更新會被合併 , 當你調用 setState() 的時候,React 會把你提供的對象合併到當前的 state。

七、 向事件處理程序傳遞參數

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
  <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上述兩種方式是等價的,分別經過箭頭函數和 Function.prototype.bind 來實現。

八、ref 和回調 refs

① Refs 提供了一種方式,容許咱們訪問 DOM 節點或在 render 方法中建立的 React 元素。

② 回調 Refs :

function CustomTextInput(props) {
        return (
            <div>
            <input ref={props.inputRef} />
            </div>
        );
    }

    class Parent extends React.Component {
        render() {
            return (
            <CustomTextInput
                inputRef={el => this.inputElement = el}
            />
            );
        }
    }

九、 去除某頻繁改動的組件的沒必要要的重複渲染

① 把該組件改成 PureComponent 先進行前比較(通常就不會再重複屢次渲染了)
② 使用 shouldComponentUpdate 進行再比較
shouldComponentUpdate ( nextProps,nextState){
        return (nextProps.xxx !== this.props.xxx) || (nextState.yyy !== this.state.yyy)
    }
好比:下面這樣在TodoList中某個 ListItem 改變就不會渲染全部的 ListItem 了。
import React, { PureComponent } from 'react'
    const noop = ()=>{}
    export default class ListItem extends pureComponent {
        handleCheckboxChange = ()=>{
             /* this.props.completedChange && this.props.completedChange(this.props.id) */
            const { completedChange=noop, id } = this.props
            /* completedChange && completedChange(id) */
            completedChange(id)
        }
        handleDeleteItem = ()=>{
            this.props.deleteItem(this.props.id)
        }
        render() {
            return (           
                <li>
                    <input type="checkbox" checked={this.props.isCompleted} onChange={ this.handleCheckboxChange} />
                    <span> {this.props.title} {this.props.isCompleted?'已完成😂':'未完成😢'} </span>  
                    <button  onClick={ this.handleDeleteItem} >刪除 </button>               
                </li>
    
            )
        }
    }

10 週期鉤子static getDerivedStateFromProps (props){} 的用法

示例:

static  getDerivedStateFromProps (props){
       return {
            completedText: props.isCompleted ? '完成' : '未完成'
       }
   }

十一、 函數式組件也能添加狀態了(React新特性 hook ,它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。)

import React, { useState, useEffect} from 'react'    // useState和 useEffect 是 hook 常見的兩個API
const Counter = ()=> {
    const [count,setCount] = useState(0)  //useState是一個方法,該方法的參數就是默認值,結果是一個數組,數組的第一個參數就是state,第二個就至關於setState
    const [title,setTitle] = useState('數字遞增或遞減') //一個函數式組件能夠有多個state
    useEffect(()=>{
      // useEffect 的參數是一個回調函數,無論組件掛載仍是更新,都會觸發這個回調,相似於 componentDidMount 和 componentDidUpdate 的結合
      console.log('渲染了')
      document.title = '當前的數是${count}'
    })
    return (
      <div>
        <p>{title}</p>
        { /* 這裏的setCount就是useState所生成的方法,注意和setState不一樣的地方在於參數,這裏的參數就是一個新值便可 */}
        <button onClick={()=>{ setCount(count-1)}}> - </button>
        <span>{count}</span>
        <button onClick={()=>{ setCount(count+1)}}> + </button>
      </div>
    )
}

十二、context 全局傳參 (不過通常用redux)

示例以下:

// createContext 是react 提供的用於跨多級組件傳值的方法
    import React , {Component, createContext} from 'react'
    import {render} from 'react-dom'

    // createContext 這個方法的結果是一個對象,裏面有兩個組件:Provider 和 Consumer , 其中 Provider 用於提供狀態, Consumer 用於接收狀態
    const {
      Provider,
      Consumer: CounterConsumer  
      // 解構出來的 Consumer 從新賦值爲 CounterConsumer 組件
    } = createContext()
    
    // 封裝 Provider ,由於直接使用 Provider 不方便管理狀態
    class CounterProvider extends Component {
      constructor(){
        super()
        // 這裏的狀態是共享的,任何 CounterProvider 的後代組件均可以經過 CounterConsumer 來接收 Provider提供的 value 值。
        this.state = {
          count: 100
        }
      }
      
      //下面的兩個方法也能夠 傳遞下去
      incrementCount = () =>{
        this.setState({
          count: this.state.count + 1
        })
      }
      
      decrementCount = () =>{
        this.setState({
          count: this.state.count - 1
        })
      }
    
      render (){
        return (
          // 使用Provider 組件,必需要有一個 value 值,用於向後代組件傳值,通常是一個對象,由於對象比較靈活。
          <Provider value={{
            this.state.count,
            onIncrementCount: this.incrementCount
            onDecrementCount: this.decrementCount
            }}>
            { this.props.chidren}
          </Provider>
        )
      }
    }
    
    class Counter extends Component {
      render (){
        return (
          // Provider 的後代組件經過 Consumer 組件來接收 Provider 提供的 value 值
          <CounterConsumer>
            {
              //  注意! Consumer 的 children 必須是一個方法,且該方法接收的參數便是 Provider 的 value 值
              ({count})=>{   // 解構出 count
                return <span>{count}</span>
              }
            }
          </CounterConsumer>
        )
      }
    }
    
    class CountBtn extends Component {
      render (){
        return (
          {
            ({onIncrementCount,onDecrementCount}) => {
              const handlerClick = this.props.type === 'increment' ? onIncrementCount : onDecrementCount
              return <Button onClick ={ handlerClick}> {this.props.children} </Button>
            }
          }
          <Button > {this.props.chidren} </Button>
        )
      }
    }
    
    class App extends Component {
      render (){
        return (
          <>
            <CountBtn type="decrement" />
            <Counter />
            <CountBtn type="increment" />
          </>
        )
      }
    }
    
    render(
      // Provider 組件 向後代傳值的形式
      <CounterProvider>
        <App />
      </CounterProvider>,
      document.getElementById('root')
    )

1三、 HOC 高階組件

import React ,{ Component} from 'react'

    const withCopyright = (YourComponent) =>{
      return class WithCopyright extends Component {
        render (){
          return (
            <>
              // 高階組件攔截了 YourComponent 傳入參數,再把參數還給 YourComponent(本身理解的 )
              <YourComponent {...this.props} />
              <div> &copy; 2019 &emsp; 千鋒教育 </div>
            </>
          )
        }
      }
    }
    
    @ withCopyright
    class YourComponent extends Component {
      render(){
        return (
          <div name="傳遞給高階組件的值">
            要添加高階組件的組件 
          </div>
        )
      }
    }
讓 create-react-app 支持@ 裝飾器寫法 ,須要作的配置:
一、無論你是要配置什麼,咱們最好的方式是使用 react-app-rewired 這個包來對cra 建立 的項目進行略微 的配置調整, `npm install customize-cra --save-dev`
二、 安裝好以後,在package.json 裏把 scripts 裏的 react-scripts 替換成 react-app-rewired 
三、 在根目錄下建立一個 config-overrides.js
module.exports = (config) =>{
          // 若是沒使用customize-cra,在這裏配置
          return config
        }
四、 固然若是想要配置更方便,能夠再安裝 customize-cra,`npm install customize-cra --save-dev`,而後把 config-overrides.js 改爲這樣
const {override,addDecoratorsLegacy} = require('customize-cra')
        module.exports = override(
          addDecoratorsLegacy()
        )
五、 `npm i @babel/plugin-proposal-decorators -D`

1四、 redux

① Flux 架構思想
* view 視圖層
* ActionCreator(動做創造者) : 視圖層發出的消息(好比 mouseClick)
* Dispatcher(派發器): 用來接收 Actions 、 執行回調函數
* Store(數據層): 用來存放應用的狀態,一旦發生變更,就提醒 Views 要更新頁面
② Flux 的流程:
1. 組件獲取到store中保存的數據掛載在本身的狀態上
二、用戶產生了操做,調用actions的方法
三、actions接收到用戶的操做,進行一系列的邏輯判斷、異步操做
四、而後actions會建立出對應的action,action帶有標識性的屬性
五、actions調用dispatcher的dispatch方法,將action傳遞給dispatcher
六、dispatcher接收到action並根據標識信息判斷以後調用store的更改數據的方法
七、store的方法被調用後,更改狀態,並觸發本身的某一個事件
八、store更改狀態後事件被觸發,該事件的處理程序會通知view去獲取最新的數據
③ Redux: Flux架構思想的一種實現
Redux 使用的三大原則:
一、single Source of Truth(惟一的數據源)
二、State is read-only (狀態是隻讀的)
三、Changes are made with pure function(數據的改變必須經過純函數完成)
④ redux 原理

<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title> Redux 原理 </title>
    </head>
    <body>
      <div>
        <button onClick="dispatch({type:"DECREASEMENT",n:2})">-</button>
        <span id="countDisplay">10</span>
        <button onClick="dispatch({type:"INCREASEMENT",n:1})">+</button>
      </div>
      <script>
        const countDisplay = document.querySelector('#countDisplay')
        const countState = {
          count: 5
        }
        const renderCount = (state) =>{
          countDisplay.innerHTML = state.count
        }
        renderCount(countState)

        const reducer = (state,action)=>{
          if(!state){
            return countState
          }
          switch(action.type){
            case 'DECREASEMENT':
              return {
                ...state,
                count: state.count - action.n
              }
            case 'INCREASEMENT':
              return {
                ...state,
                count: state.count + action.n
              }
            default:
              break
          }
        }
    
        const createStore = (reducer)=>{
          let state = null
          const getState = ()=> state
          const listeners = []
          const subscribe = (listener)=>listeners.push(listener)
          const dispatch = (action)=>{
            reducer(state,action)
            listeners.forEach(listener =>listener())
          }
          return {
            getState,
            subscribe,
            dispatch
          }
        }
    
        const store = createStore(reducer)
        store.subscribe(renderCount)
    
      </script>
    </body>
    </html>
  • redux 和react-redux 使用流程: 建立reduers -> 合併reducers -> 建立store=createStore(reducer) -> 頂層組件經過Provider使用store,<Provider store={store}> -> 後代組件經過 connect 接收store,connect(mapStateToProps,{...actionCreators})(YourComponent) -> 建立 action -> 根據action.type 配置actionCreators
  • react-redux 處理流程: actionCreator -> 自動 dispatch(actionCreator()) -> reducer -> store -> view -> 自動 dispatch(actionCreator())...
  • 處理異步action 流程: actionCreator -> middleware處理生成新的action -> 修改dispatch(action) -> reducer -> store -> view -> dispatch(actionCreator())

1五、react-router

import React from 'react'
import { render } from 'react-dom'
import { BrowerRouter as Router, Route } from 'react-router-dom'
import App from './App'

render(
    {/* Router 組件只用在頂層,Route組件用在各個要跳轉的後代組件上*/}
    <Router>
        <Route component={App} path='/' />
    </Router>,
    document.querySelector('#root')
)

App.js:

import React, {Component} from 'react'
import { Home,Article,User,NotFound} from './views'
import {ArticleDetail} from './Article/ArticleDetail'
// NavLink 組件比Link組件多了一個active的class,Redirect組件要搭配Switch使用
import {Route, NavLink as Link, Redirect, Switch } from 'react-router-dom'

export default class App extends Component {
    render(){
        return (
            <div>
                <ul>
                    <li> <Link to='/home'>首頁</Link> </li>
                    <li> <Link to='article'>文章</Link> </li>
                    <li> <Link to='/user'>用戶</Link></li>
                </ul>
                {/* Switch 組件包含的路由匹配成功一個,便返回再也不往下匹配*/}
                <Switch>
                    {/* Route包含的組件默認都有history、props等屬性 */}
                    <Route component={Home} path='/home' />
                    {/* 加入exact屬性,表示不匹配子路由,默認是匹配該路徑和其子路徑的 */}
                    <Route component={Article} path='/article' exact />
                    {/* 匹配成功的話,這裏的文章詳情將覆蓋Article */}
                    <Route component={ArticleDetail} path="/article/:id" />
                    {/* 用render渲染組件傳遞參數 */}
                    <Route render={(routeProps)=>{ 
                        return this.state.isLogin ? <User {...routeProps} /> : <div>請登陸</div>}} path='/user' />
                    <Redirect to="/home" from="/" exact />
                    <Route Component={NotFound} path="/404" />
                    <Redirect to="/404" />
                </Switch>
            </div>
        )
    }
}

views/articles/index.js:

import React,{Component} from 'react'
import { Link, Route } from 'react-dom'
//import ArticleDetail from './Article/ArticleDetail'
import {* as Home} from './backhome'
export default class Article extends Component {
    render(){
        return (
            <div>
            {/* query傳參 */}
              <Link to="/article/1?from=article"> 文章1 </Link>
            {/* 隱式傳參,好比能夠作埋點 */}
              <Link to={{pathname:'artical/2',state:{ from:'article'}}}> 文章2 </Link> 
            
             {/* <Route component={ArticleDetail} path="/article/:id" /> */}

             <Home />
            </div>
        )
    }
}

goHome = ()=> {
    this.props.history.push({
        pathname:'/home',
        state:{
            id: this.props.match.params.id
        }
    })
}

backhome.js :

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

class BackHome extends Component {
    handleClick = () => {
        this.props.history.push({
            pathname: '/home',
            state:{
                id: this.props.match.params.id
            }
        })
    }
    render(){
        return (
            <button onClick={this.handleClick}>返回首頁</button>
        )
    }
}
export default withRouter(BackHome)

1六、經常使用富文本編輯器: KindEditor、 UEditor(百度的)、 wangeditor、 edit.md(支持markdown) 等

  • 富文本編輯器原理:
<button onclick="boldFont()">加粗<button> 
    <button onclick="italicFont()">傾斜<button> 
    <button onclick="redFont()">字體紅色<button> 

    <div contenteditable style="border:1px solid #dedede; width:400px; min-height:300px;">
        <img src="http://img5.imgtn.bding.com/it/u=394753335xxxxxxx&gp=0.jpg">
    </div>

    <!-- 老瀏覽器用<iframe designmode="on"  /> -->

    <script>
        var boldFont = function(){
            document.execCommand('bold')
        }
        var italicFont = function(){
            document.execCommand('italic')
        }
        var redFont = function(){
            document.execCommand('foreColor',null,'#f00')
        }
    </script>

1七、數據可視化:

一、canvas 位圖  二、 svg 矢量圖  三、 三維 webgl
√四、echart 五、highcharts 六、d3 dataV antV 
 egret 遊戲   rapheal.js等

1八、數據深拷貝

import {cloneDeep} from 'lodash'
const state = {
    name:'可經過assign方法淺拷貝的基本數據類型的數據,拷貝後的數據改變不影響源數據中該項的數據',
    items:['經過淺拷貝後實際是拷貝過去的該複雜數據的地址','拷貝後的數據中該複雜數據項改變的話,源數據跟着變化']
}
// const newState = JSON.parse(JSON.stringify(state))  //該深拷貝方式不能拷貝方法
const newState = cloneDeep(state)
console.log(newState === state)

1九、 immutable 實現持久化數據結構

  • immutable 插件中經常使用的數據類型有 Map 、List、Set等
import {Map} from 'immutable'
const state={
    name:'map',
    value:['對象','方法','數組']
}
const imState = Map(state)
//比較兩個數據結構的不一樣
console.log(state,imState)
//改變後必需要接收新的map對象
const newImState = imState.set('name','複雜數據做爲鍵')
//map數據只能經過get獲取
console.log(imState.get('name'),newImState.get('name'))

const map1 = Map({a:1,b:2});
const map2 = Map({a:1,b:2});
const map3 = map1.set('b',2);
const mapCopy = map1;
map1.equals(map2); // true
is(map1,map2); // true
map1 === map2; // false
map1 === map3; // true
import {List} from 'immutable'
const list1 = List([1,2]);
const list2 = list1.push(3,3,4);
console.log(list1.get(4),list2.get(4)) // undefined 3
import {fromJS,toJS} from 'immutable'
const state = {
    name: 'qf',
    courses:['h5','java','python'],
    obj:{
          x:1,
          y:{
              z:1
          }
        }
}

//複雜的JS數據類型轉換爲immutable,很經常使用
const imState = fromJS(state)  
console.log(imState.get('courses').get(0))
console.log(imState.getIn(['courses',0]))
console.log(imState.getIn(['obj','x','y']))
const newImState = imState.setIn(['obj','y','z'],100)
const newImState = imState.updateIn(['obj','y','z'],v=>v+1)
const jsState = newImState.toJS().obj.y.z
  • 使用 immutable、react-immutable改造redux中reducer的state數據類型:

    一、 `npm i immutable redux-immutable -S`
    二、reducers文件夾counter.js中修改 
      import {combineReducers} from 'redux'  
     --->  import {combineReducers} from 'redux-immutable'
    三、reducers文件夾counter.js中修改
      const initState = { count:10 }  
     ---> import { fromJS} from 'immutable' 
          const initState = fromJS({ count:10 })
    四、reducers文件夾counter.js中修改 
       case 'INCREAMENT': return { ...state, count:state.count+1 }
      ---> case 'INCREAMENT': return  state.updateIn(['count'],v=>v+1)
       case 'DECREAMENT': return { ...state, count:state.count-1 }
      ---> case 'DECREAMENT': return  state.update('count',v=>v-1)
    五、在調用conncect的組件中修改
      const mapStateToProps = state =>{
          return {
              count:state.counter.count
             ---> count: state.getIn(['counter','count'])
          }
      }

20、mobx 更簡單的狀態管理,但它是直接修改狀態,非純函數有反作用

​ 一、npm i mobx mobx-react -S

​ 二、store.js:

import {observable,computed,action} from 'mobx'
 class Counter {
   name = 'Counter App'
   @observable count = 100
   @computed get doubleCount(){
     return this.count*2
   }
   @action.bound increment(){
     this.count += 1
   }
 }
 
 const counterState = new Counter()
 export default counterStore

index.js:

import React from 'react'
import ReactDom from 'react-dom'
import {Provider} from 'mobx-react'
import App from '/App'
import couterStore from './store'

ReactDom.render(
    <Provider counter={counterStore}>
      <App />
    </Provider>,
    document.getElementById('root')
)

App.js:

import React,{Component} from 'react'
import {inject,observer} from 'mobx-react'

@inject('counter')
@observer
export default class App extends Component{
    render(){
        return (
            <div>
                <CounterDisplay counter={this.props.counter} />
                <button onClick={this.props.counter.increment}>+</button>
            </div>
        )
    }
}

@inject((store)=>{
    return {
        count:store.counter.count,
        doubleCount:store.counter.doubleCount
    }
})
@observer
class CounterDisplay extends Component{
    render(){
        return (
            <span>原值:{this.props.count}</span> 
            <span>二倍值:{this.props.doubleCount}</span>
        )
    }
}

2一、immutable、react-immutable 和 mobx、mobx-react 完成 redux 改造(不必,我的感受redux保存初始數據且可維護性更好)

相關文章
相關標籤/搜索