Redux——结合案例一步步深入学习

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

// State 是一个对象

function reducer(state, action) {

return Object.assign({}, state, { thingToChange });

// 或者

return { …state, …newState };

}

// State 是一个数组

function reducer(state, action) {

return […state, newItem];

}

最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。

TodoList案例

=====================================================================

实现效果


在这里插入图片描述

在这里插入图片描述

实现思路


通过npm i redux安装redux

1.封装组件

src/index.js

import React from ‘react’;

import ReactDOM from ‘react-dom’;

import App from ‘./App’;

import store from “./store”

window.store = store;

//组件可以通过store.getState()获取到新的状态了,并且reducer返回什么,这个结果就是什么

//console.log(store) {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}

// console.log(store.getState()) 得到数据

ReactDOM.render(

,

document.getElementById(‘root’)

);

src/App.js

import React,{Component} from ‘react’;

import TodoList from “./components”

class App extends Component{

render(){

return (

)

}

}

export default App;

src/components/index.js

import React, { Component } from ‘react’

import TodoInput from “./TodoInput”

import TodoContent from “./TodoContent”

import TodoInfo from “./TodoInfo”

export default class index extends Component {

render() {

return (

)

}

}

2.redux的实现

store/actionType.js

action上的标示性信息

如果在actionCreators里面将action的type写错名字了,那么就在reducers里面匹配不上,内部就会走default

返回了之前的状态。

而redux的思想:状态与视图是一一对应的。状态不变,那么组件的视图结构就不会发生任何的变化

export const ADD_NEW_TODO=“ADD_NEW_TODO”

export const CHANGE_NEW_TODO=“CHANGE_NEW_TODO”

export const REMOVE_NEW_TODO=“REMOVE_NEW_TODO”

store/actionCreators.js

组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer

import store from “./index”

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from “./actionType”

export default{ //面试题? actionCreators or action 关系? actionCreators创建action action 一般是一个对象 type 通常写为type

addNewTodo(title){

let action={ //一般的action是具有标志性的信息的对象

type:ADD_NEW_TODO,

title

}

//需要将action给reducer传递过去

store.dispatch(action)

},

changeNewTodo(id){

let action={

type:CHANGE_NEW_TODO,

id

}

store.dispatch(action)

},

removeNewTodo(id){

let action={

type:REMOVE_NEW_TODO,

id

}

store.dispatch(action)

}

}

store/state.js

初始数据,reducer中参数默认值

export default {

todos:[

{id:1,title:“今天周一”,isFinished:false},

{id:2,title:“检查作业”,isFinished:true}

]

}

store/reducer.js

创建一个reducer,然后将其传入到createStore中辅助store的创建

想要给store创建默认状态其实就是给reducer一个参数创建默认值

reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变 reducer返回什么状态,store.getState就可以获取什么状态

redux内部判别是否派发新状态依据是根据原状态的内存地址与新状态的内存地址的比较

//reducer必须是一个纯函数

//固定的输入必须要有固定的输出,内部函数不能有不纯粹的操作 Math.random() new Date()

//不能对之前的状态进行任何的更改 内部必须是同步的

//redux思想 状态与视图是一一对应的

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from “./actionType”

import state from “./state”

const reducer = (prevState = state,action)=>{ //state={todos:[]}

let new_state={…prevState}

switch(action.type){

case ADD_NEW_TODO:

new_state.todos.push({id:handler.getId(new_state.todos),title:action.title,isFinished:false})

break;

case CHANGE_NEW_TODO:

new_state.todos=handler.changeNewTodo(new_state.todos,action.id)

break;

case REMOVE_NEW_TODO:

new_state.todos=handler.removeNewTodo(new_state.todos,action.id)

break;

default:

break;

}

return new_state //通过store.getState()其实就是获取到了reducer返回的内容

}

export default reducer

const handler={

getId(todos){

todos=todos.slice()

if(todos.length === 0) return 1;

return todos.sort((a,b)=>{

return b.id-a.id

})[0].id+1

},

changeNewTodo(todos,id){

for(var i=0;i<todos.length;i++){

if(todos[i].id===id){ //正是想要更改的这一项

todos[i].isFinished=!todos[i].isFinished

}

}

return todos

},

removeNewTodo(todos,id){

todos=todos.slice()

return todos.filter(item=> item.id!==id)

}

}

store/index.js

从redux工具中取出createStore去生成一个store,可以通过createStore()创建一个store,参数需要接受reducer

import {createStore} from “redux”

import reducer from “./reducer”

//可以通过createStore()创建一个store,参数需要接受reducer

const store = createStore(reducer)

export default store

3.组件

组件通过调用store.getState方法来使用store中的数据

我们可以在组件中,利用store.subscribe(callback)方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态

src/components/TodoContent

列表显示

import React, { Component } from ‘react’

import store from “…/store”

import actionCreators from “…/store/actionCreators”

//函数式组件(无状态组件)

const LiItem=props=>{

const handleChange=()=>{

//需要更改redux的状态,所以应该派发action进行更改

actionCreators.changeNewTodo(props.item.id)

}

const handleRemove=()=>{

actionCreators.removeNewTodo(props.item.id)

}

return(

  • {props.item.title}

    删除

    )

    }

    export default class TodoContent extends Component {

    constructor(){

    super()

    this.state={

    todos:[]

    }

    }

    setTodos=()=>{

    this.setState({

    todos:store.getState().todos

    })

    }

    componentDidMount(){

    this.setTodos()

    //组件想要获取redux最新状态的话,需要调用store.subscribe订阅方法才可以

    //监控reducer的新状态的返回,如果reducer返回新状态,那么这个callback就会执行

    store.subscribe(()=>{

    this.setTodos()

    })

    }

    renderItem(){

    let {todos}=this.state

    return (

    todos.map(item=>{

    return (

    <LiItem item={item} key={item.id }/>

    )

    })

    )

    }

    render() {

    return (

      {this.renderItem()}

      )

      }

      }

      src/components/TodoInfo

      计算显示数量

      import React, { Component } from ‘react’

      import store from “…/store”

      export default class Todoinfo extends Component {

      constructor(){

      super()

      this.state={

      todos:store.getState().todos

      }

      }

      computed(){

      let {todos}=this.state

      let all={total:0,finish:0,unfinish:0}

      if(todos.length===0) return all;

      all.total = todos.length

      todos.forEach(item=>{

      if(item.isFinished){

      all.finish++

      }else{

      all.unfinish++

      }

      })

      return all

      }

      componentDidMount(){

      //订阅状态的变化

      store.subscribe(()=>{

      this.setState({

      todos:store.getState().todos

      })

      })

      }

      render() {

      let all=this.computed()

      return (

      总共{all.total}条

      完成了{all.finish}条

      未完成{all.unfinish}条

      )

      }

      }

      src/components/TodoInput

      输入框

      import React, { Component, createRef } from ‘react’

      import actionCreators from “…/store/actionCreators”

      export default class TodoInput extends Component {

      constructor() {

      super()

      this.input = createRef()

      }

      handleKeyUp = (e) => {

      if (e.keyCode === 13) {

      //通过actionCreators创建action派发给reducer进行处理

      actionCreators.addNewTodo(this.input.current.value)

      this.input.current.value = “”

      }

      }

      render() {

      return (

      )

      }

      }

      TodoList + Calculator + react-redux 案例

      =================================================================================================

      官方网站

      • 通过npm i react-redux安装react-redux

      • 在之前的案例里我们发现

      我们发现我们使用redux状态的时候,

      我们发现在组件里面都需要引入store

      定义组件的初始化状态

      监听redux状态的变化

      • 如何使用react-redux

      需要在src/index.js上面使用Providestore属性

      需要在components/todolist/TodoInfo.js文件使用redux状态

      • 使得actionCreators变得更加的纯粹,只需要return action就可以了。通过connect()高阶组件

      const mapDispatchToProps = actionCreators

      export default connect(null,mapDispatchToProps)(TodoInput)

      connect(mapState,mapDispatch)(reactUI组件)

      第一个mapState就是可以让组件通过属性props的方式去访问redux的共享状态

      第二个mapDispatch可以是一个函数Object可以让组件通过属性的方式去访问到更改redux状态的方法了,并且派发action到reducer

      实现效果


      在这里插入图片描述

      在这里插入图片描述

      实现思路


      1.封装组件

      src/App.js

      import React,{Component} from ‘react’;

      import TodoList from “./components/todolist”

      import Calculator from “./components/calculator”

      class App extends Component{

      render(){

      return (

      )

      }

      }

      export default App;

      src/index.js

      需要在src/index.js上面使用Providestore属性

      import React from ‘react’;

      import ReactDOM from ‘react-dom’;

      import App from ‘./App’;

      import store from “./store”

      import {Provider} from “react-redux”

      ReactDOM.render(

      //这边通过store属性传下去,那么所有的Provider的后代组件都可以通过connect连接,然后获取redux的状态了

      ,

      document.getElementById(‘root’)

      );

      2.redux 和 react-redux

      reducer状态的拆分

      src/store/index

      import {createStore} from “redux”

      import reducer from “./reducer” //这个地方的reducer是合并好了的reducer

      //可以通过createStore()创建一个store,参数需要接受reducer

      const store = createStore(reducer)

      export default store

      src/store/reducer.js

      通过combineReducers来去合并所有的分支reducer

      import {combineReducers} from “redux”

      import {combineReducers} from “redux”

      import todolist from “./todolist/reducer”

      import calculator from “./calculator/reducer”

      //通过combineReducers来去合并所有的分支reducer

      const reducer =combineReducers({

      todolist,

      calculator

      })

      export default reducer

      TodoList

      src/store/todolist/state.js

      export default {

      todos:[

      {id:1,title:“今天周一”,isFinished:false},

      {id:2,title:“检查作业”,isFinished:true}

      ]

      }

      src/store/todolist/reducer.js

      //reducer必须是一个纯函数

      //固定的输入必须要有固定的输出,内部函数不能有不纯粹的操作 Math.random() new Date()

      //不能对之前的状态进行任何的更改 内部必须是同步的

      //redux思想 状态与视图是一一对应的

      //新的状态与之前的状态通过引用地址进行比较的,如果操作的新状态的地址与原状态一样,内部就会认为没有返回新的状态,那么视图也不会有新的变化

      import state from “./state”

      import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from “./actionType”

      const reducer = (prevState = state,action)=>{ //prevState={todos:[]}

      let new_state={…prevState} //new_state={todos:[]} 浅拷贝 {…}object.assign vs 深拷贝 (递归, 对象 JSON stringify parse)

      switch(action.type){

      case ADD_NEW_TODO:

      new_state.todos=new_state.todos.slice() //将之前的状态拷贝出来新的一份地址不一样

      new_state.todos.push({id:handler.getId(new_state.todos),title:action.title,isFinished:false})

      break;

      case CHANGE_NEW_TODO:

      new_state.todos=handler.changeNewTodo(new_state.todos,action.id)

      break;

      case REMOVE_NEW_TODO:

      new_state.todos=handler.removeNewTodo(new_state.todos,action.id)

      break;

      default:

      break;

      }

      return new_state //通过store.getState()其实就是获取到了reducer返回的内容

      }

      export default reducer

      const handler={

      getId(todos){

      todos=todos.slice()

      if(todos.length === 0) return 1;

      return todos.sort((a,b)=>{

      return b.id-a.id

      })[0].id+1

      },

      changeNewTodo(todos,id){

      todos=todos.slice()

      for(var i=0;i<todos.length;i++){

      if(todos[i].id===id){ //正是想要更改的这一项

      todos[i].isFinished=!todos[i].isFinished

      }

      }

      return todos

      },

      removeNewTodo(todos,id){

      todos=todos.slice()

      return todos.filter(item=> item.id!==id)

      }

      }

      src/store/todolist/actionType.js

      import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from “./actionType”

      export default{ //面试题? actionCreators or action 关系? actionCreators创建action action 一般是一个对象 type 通常写为type

      addNewTodo(title){

      let action={ //一般的action是具有标志性的信息的对象

      type:ADD_NEW_TODO,

      title

      }

      //需要将action给reducer传递过去

      return action

      },

      changeNewTodo(id){

      let action={

      type:CHANGE_NEW_TODO,

      id

      }

      return action

      },

      removeNewTodo(id){

      let action={

      type:REMOVE_NEW_TODO,

      id

      }

      return action

      }

      }

      src/store/todolist/actionCreators

      import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from “./actionType”

      export default{ //面试题? actionCreators or action 关系? actionCreators创建action action 一般是一个对象 type 通常写为type

      addNewTodo(title){

      let action={ //一般的action是具有标志性的信息的对象

      type:ADD_NEW_TODO,

      title

      }

      //需要将action给reducer传递过去

      return action

      },

      changeNewTodo(id){

      let action={

      type:CHANGE_NEW_TODO,

      id

      }

      return action

      },

      removeNewTodo(id){

      let action={

      type:REMOVE_NEW_TODO,

      id

      }

      return action

      }

      }

      Calculator

      src/store/caculator/state.js

      export default{

      prevNumber:0,

      nextNumber:0,

      operator:“+”,

      result:0

      }

      src/store/caculator/reducer.js

      import state from “./state”

      import {CHANGE_NUMBER,COMPUTE} from “./actionType”

      const reducer=(prevState=state,action)=>{

      let new_state={…prevState}

      switch(action.type){

      case CHANGE_NUMBER:

      new_state=handler.changeNumber(new_state,action.payload.numType,action.payload.num)

      break;

      case COMPUTE:

      new_state=handler.compute(new_state)

      break;

      default:

      break;

      }

      return new_state

      }

      export default reducer

      const handler={

      changeNumber(state,numType,num){ //new_state={pervNumber,nextNumber}

      state[numType]=num

      return state

      },

      compute(state){

      state[“result”]=state.prevNumber1+state.nextNumber1

      return state

      }

      }

      src/store/caculator/actionType.js

      export const CHANGE_NUMBER=“CHANGE_NUMBER”

      export const COMPUTE=“COMPUTE”

      src/store/caculator/actionCreator.js

      import {CHANGE_NUMBER,COMPUTE} from “./actionType”

      export default{

      changeNumber(numType,num){ //这些方法成为actionCreators

      let action={

      type:CHANGE_NUMBER,

      payload:{

      numType,

      num

      }

      }

      return action

      },

      compute(){

      let action={

      type:COMPUTE

      }

      return action

      }

      }

      src/group/caculator-group.js

      import {connect} from “react-redux”

      import actionCreators from “…/calculator/actionCreators”

      export default connect(state=>state.calculator,actionCreators)

      3.组件

      TodoList组件

      src/components/todolist/index.js

      import React, { Component } from ‘react’

      import TodoInput from “./TodoInput”

      import TodoContent from “./TodoContent”

      import TodoInfo from “./TodoInfo”

      export default class index extends Component {

      render() {

      return (

      )

      }

      }

      src/components/todolist/TodoContent.js

      import React, { Component } from ‘react’

      import actionCreators from “…/…/store/todolist/actionCreators”

      import { connect } from “react-redux”

      //函数式组件(无状态组件)

      const LiItem = props => {

      const handleChange = () => {

      //需要更改redux的状态,所以应该派发action进行更改

      props.pp.changeNewTodo(props.item.id)

      }

      const handleRemove = () => {

      props.pp.removeNewTodo(props.item.id)

      }

      return (

    • {props.item.title}

      删除

      )

      }

      class TodoContent extends Component {

      renderItem() {

      // console.log(this.props) //{todos,三个更改状态的方法} {todos: Array(2), addNewTodo: ƒ, changeNewTodo: ƒ, removeNewTodo: ƒ}

      let { todos } = this.props

      return (

      todos.map(item => {

      return (

      )

      })

      )

      }

      render() {

      return (

        {this.renderItem()}

        )

        }

        }

        //connect(mapStateToProps,mapDispatchToProps)(UIComponent)

        //mapStateToProps是一个函数,这个函数参数是state,可以让UIComponent通过this.props获取到redux的状态。

        //是一个函数,这个函数参数是dispatch,可以让UIComponent通过this.props获取到更改redux状态的actionCreators方法。

        const mapStateToProps = state => {

        return state.todolist

        }

        const mapDispatchToProps = actionCreators

        export default connect(mapStateToProps, mapDispatchToProps)(TodoContent)

        src/components/todolist/TodoInfo.js

        import React, { Component } from ‘react’

        import {connect} from “react-redux”

        class TodoInfo extends Component {

        computed() {

        let { todos } = this.props

        let all = { total: 0, finish: 0, unfinish: 0 }

        if (todos.length === 0) return all;

        all.total = todos.length

        todos.forEach(item => {

        if (item.isFinished) {

        all.finish++

        } else {

        all.unfinish++

        }

        })

        return all

        }

        //这个钩子函数只有当外部传入进来的属性发生变化的时候,此钩子函数才会执行

        UNSAFE_componentWillReceiveProps(){

        console.log(111111)

        }

        render() {

        let all = this.computed()

        return (

        总共{all.total}条

        完成了{all.finish}条

        未完成{all.unfinish}条

        )

        }

        }

        //connect()(UI组件/傻瓜组件木偶组件/展示组件)===>返回一个容器组件/智能组件

        //connect() 是一个HOC高阶组件,本质上就是一个函数,可以接收一个UI组件,返回一个新的组件 /shouldComponent PureComponent React.memo(组件)

        //这个函数返回什么,TodoInfo这个组件的属性上面就会有什么

        //我们发现参数上面的state其实就是store.getState()之后的结果

        //其实当redux的状态发生变化的时候,容器组件可以监听到状态的变化,然后把最新的状态通过属性的方式传递给了TodoInfo组件

        //因为容器组件内部已经帮助我们实现了store.subscribe方法的订阅了

        const mapStateToProps=state=>{

        return state.todolist

        }

        export default connect(mapStateToProps)(TodoInfo)

        src/components/todolist/TodoInput

        import React, { Component, createRef } from ‘react’

        import actionCreators from “…/…/store/todolist/actionCreators”

        import { connect } from ‘react-redux’

        class TodoInput extends Component {

        constructor() {

        super()

        this.input = createRef()

        }

        handleKeyUp = (e) => {

        if (e.keyCode === 13) {

        //通过actionCreators创建action派发给reducer进行处理

        this.props.addNewTodo(this.input.current.value)

        this.input.current.value = “”

        }

        }

        render() {

        return (

        )

        }

        }

        //这个方法返回什么,UI组件的属性上面就会有什么

        //方法作用就是可以在这里面挂载一些更改redux状态的方法,UI组件可以通过属性去访问到

        // const mapDispatchToProps=dispath=>{

        // return {

        // addNewTodo:title=>{

        // let action=actionCreators.addNewTodo(title)

        // dispath(action)

        // }

        // }

        // }

        //将所有的更改redux的状态的方法全都挂载到UI组件的属性上面去,并且内部会自动的将action派发给reducer

        const mapDispatchToProps = actionCreators

        export default connect(null,mapDispatchToProps)(TodoInput)

        Calculator组件

        src/components/calculator/index.js

        import React, { Component } from ‘react’

        import Expression from “./Expression”

        import Result from “./Result”

        export default class index extends Component {

        render() {

        return (

        =

        )

        }

        }

        src/components/calculator/Result.js

        import React, { Component } from ‘react’

        import CalculatorGroup from “…/…/store/group/calculator-group”

        class Result extends Component {

        render() {

        return (

        {this.props.result}

        )

        }

        }

        export default CalculatorGroup(Result)

        src/components/calculator/Expression.js

        import React, { Component, Fragment } from ‘react’

        import CalculatorGroup from “…/…/store/group/calculator-group”

        class Expression extends Component {

        handleChange=(e)=>{

        // console.log(e.target.id,e.target.value)

        this.props.changeNumber(e.target.id,e.target.value)

        }

        compute=()=>{

        this.props.compute()

        }

        render() {

        let {prevNumber,nextNumber,operator}=this.props

        return (

        **自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

        深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

        因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

        img

        既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

        由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

        如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

        结尾

        正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

        以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

        戳这里免费领取前端学习资料

        s`

        import React, { Component } from ‘react’

        import Expression from “./Expression”

        import Result from “./Result”

        export default class index extends Component {

        render() {

        return (

        =

        )

        }

        }

        src/components/calculator/Result.js

        import React, { Component } from ‘react’

        import CalculatorGroup from “…/…/store/group/calculator-group”

        class Result extends Component {

        render() {

        return (

        {this.props.result}

        )

        }

        }

        export default CalculatorGroup(Result)

        src/components/calculator/Expression.js

        import React, { Component, Fragment } from ‘react’

        import CalculatorGroup from “…/…/store/group/calculator-group”

        class Expression extends Component {

        handleChange=(e)=>{

        // console.log(e.target.id,e.target.value)

        this.props.changeNumber(e.target.id,e.target.value)

        }

        compute=()=>{

        this.props.compute()

        }

        render() {

        let {prevNumber,nextNumber,operator}=this.props

        return (

        **自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

        深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

        因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

        [外链图片转存中…(img-5AJ8pFdO-1713554796696)]

        [外链图片转存中…(img-lmwGJhzZ-1713554796697)]

        既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

        [外链图片转存中…(img-nXbOzyhE-1713554796697)]

        由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

        如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

        [外链图片转存中…(img-BF5h5TgC-1713554796697)]

        结尾

        正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

        以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

        戳这里免费领取前端学习资料

        前端学习书籍导图-1

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

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

      抵扣说明:

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

      余额充值