(小写开头的就是h5标签)这些都属于jsx语法
index.js ``文件中通过ReactDOM将APP这个组件显示在``id=root
这个标签下面
render
返回的必须是在一个大的元素之中,但是有时候又不想让这个div显示就可以用 的占位符来替代最外层的div,不会渲染为任何标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <input value={this .state.inputvalue} --inputvalue就是输入框的值 onChange={this .handleInputChange} --通过调用该函数来改变改值 /> handleInputChange=(e)=>{ this .state.inputValue = e.target .vaue console.log(e.target .value) --e.target 是Input框返回的DOM节点,e.target .value返回的就是value值 console.log(this ) this .setState({ inputValue:e.target .value }) } /*如果需要实现改变inputvalue的值就在handleInputChange函数里写this .state.inputValue = e.target .vaue 如果handleInputChange这个函数没有使用箭头函数,此时的this 为undefined,可以使用ES6的语法将this 的指向指向为Todolist组件 onChange={this .handleInputChange.bind(this )}即可。
JSX细节语法补充
1 2 3 4 5 6 7 8 9 10 11 1. 两种注释方法: { } 或者 {} 2. 为了防止某些标签的内容不被转义 也就是在输入框输入<h1>啦啦啦<h1/>点击提交的时候是显示啦啦啦 return <li key ={index} onClick ={this.handledelete} dangerouslySetInnerHTML ={{__html:item}} > </li > 3. 在输入框前添加label标签的时候 点击babel标签的内容光标会自动聚焦到输入框上 <label htmlFor='insertArea' >输入内容</label> <input id='insertArea' className='input' value={inputvalue}
注意
1 2 3 4 5 6 7 8 9 10 新版的react setState返回的不是一个对象了 要把对象替换成函数 之前: handlechange=(e )=> { this .setState( {inputvalue :e.target.value} )} 现在: const value = e.target.value this .setState(() => ({ inputvalue:value})) }
React优点
声明式开发:使得我们能减少大量DOM操作代码量
可以与其他框架并存
组件化
单向数据流
视图层框架
函数式编程 好处:用react写出来的项目更容易实现前端自动化测试
思考
单向数据流:父组件可以向子组件传值,但是子组件不能改变这个值,只能单向进行传递,不能反过来修改。这就说明为什么在修改item项目的时候,为什么不向子组件传递一个list,而要传递index与item了。
再或者说如果一个父组件有很多个子组件且子组件都共用list,如果将list传给某一个子组件且在该子组件进行修改,会导致其余四个组件中list也发生变化,这样页面出现bug的时候,就不知如何去定位这个bug,对代码的调试是很不方便的,因此要有单向数据流的概念。
如果真的想修改:首先父组件传递给子组件一个方法,在子组件去调用父组件传的这个方法,然后去做删除。
React是一个视图层的框架:react并不是什么问题都解决,只帮助你解决数据和页面渲染上的一些东西,至于组件之间怎么传值(就如1给2传递的话就不能按一层一层传了),并不负责,交给其他组件来做,做到大型项目时,react不够用,只能借助它去搭建视图,做组件传值的时候还要借助redux等的数据层的框架来做额外的支撑。
巩固知识
父组件给子组件传值的时候,子组件要对传来的值做类型的限制。如果对传来的值做了isRequired的限制,如果不传就会报错。
1 2 3 4 5 6 7 8 9 10 11 限制类型有两种放置方法: 1. TodoItem.propTypes={ item:PropTypes.string, dele:PropTypes.func, index:PropTypes.number } --放置在定义完组件的下方(最下方) 2. TodoItem.propTypes={ item:PropTypes.string, dele:PropTypes.func, index:PropTypes.number } --放置在render的上方
虚拟DOM (本质上就是一个js对象,之所以能提高性能,因为js去比较js对象不怎么耗性能,去比较真实的DOM很耗性能)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 真实DOM和虚拟DOM性能的差异: 使用真实DOM 1. state数据 **初始数据state 2. JSX 模版 **对应render里的jsx代码 3 .数据+模版结合,生成真实的DOM,来显示 4. state 发生改变 5 .数据+模版结合,生成真实的DOM,并不直接替换原始的DOM 6 .新的DOM 和原始的DOM做比对,找差异 7. 找出input框发生了变化 8. 只用新的DOM中的input元素,替换掉老的DOM中的input元素 **相比全部替换原始DOM性能提升 **但是还是消耗了一部分性能:新的DOM和原始DOM做比对的性能 性能提升也不是很明显 使用虚拟DOM: 1. state数据 2. JSX 模版 3. 数据+模版结合,生成真实的DOM,来显示 假设真实DOM为:<div id ='abc' > <span > hello world</span > </div > 4 .生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(相比上面第四步没有生成虚拟DOM 实看是损耗了性能) [ 'div' , {id : 'abc' }, [ 'span' ,{},'hello world ' ]] 5. state 发生变化 6. 数据+模版生成新的虚拟DOM(极大的提升了性能相比创建真实DOM,js对象创建的过程性能是极低的) [ 'div' , {id : 'abc' },[ 'span' , {},'bye bye' ]] 7 .比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能) **比对的是原始的js对象和新的js对象两个js对象的比较是极不损耗性能的。 8 .直接操作DOM,改变span中的内容
深入了解虚拟DOM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 实际的流程是: 1. state数据 2. JSX 模版(类似于div) 3 .数据 + 模板 生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(损耗了性能) [ 'div' , {id : 'abc' }, [ 'span' ,{},'hello world ' ]] 4. 用虚拟DOM的结构生成真实的DOM,来显示 <div id='abc' ><span > hello world</span > </div> 5. state 发生变化 6. 数据+模版生成新的虚拟DOM(极大的提升了性能) [ 'div', {id: 'abc'},[ 'span', {},'bye bye' ]] 7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能) 8.直接操作DOM,改变span中的内容 虚拟DOM的好处: 1.性能提升:因为生成真实DOM要耗费很大的性能 2.它使得跨端应用得以实现 第2点好处的底层原理:没有虚拟DOM React是无法写原生应用的,根本无法在原生(React Native)的应用里被使用。 例如:没有虚拟DOM,流程就是准备好state数据和模板之后就会渲染DOM,渲染DOM在浏览器是没有问题的,可是在移动端的原生应用里,比如安卓或者ios机器上的代码是不存在DOM这个概念的,所以没有虚拟DOM,在原生的应用里根本无法被使用,所以代码只能运行在浏览器里面,但是有虚拟DOM就不一样了,代码首先会被转化为虚拟DOM(是个js对象)js对象在浏览器可以被识别,在原生的应用里也可以被识别。 换句话说:虚拟DOM生成好了在浏览器下面,可以将虚拟DOM变成真实DOM,浏览器下面会显示出真实的页面。假设在原生应用里,虚拟DOM生成之后可以不生成真实DOM,而让虚拟DOM生成些原生的组件,这样的话同样的代码包括state,jsx的代码都可以被复用,原生的应用里面可以把对应的页面展示出来。因为生成的虚拟DOM,在哪里都可以运行,只需要在网页上将虚拟DOM转化为真实DOM,这样的话网页就可以跑起来。这使得React既能开发网页应用,还可以开发原生应用
虚拟DOM中的Diff算法
1 2 3 4 5 6 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能) 当数据发生改变的时候(state和props的改变,其实props的改变实际上也是父组件的state发生改变),虚拟DOM和真实DOM才会发生比对。 归根到底,当你调用setState的时候,数据才发生改变,然后虚拟DOM才发生比对。(setState是异步的,实际上是为了提升react底层的性能,原因:假设连续调用三次setState,变更三组数据,React会把三次setState合并成一次,只是做了一次虚拟DOM的比对,然后去更新一次DOM,可以额外减去那两次DOM比对带来的性能上的耗费)--性能优化 Diff算法就是两个虚拟DOM比对的算法,React采用的是同层的虚拟DOM比对的算法。左边的为虚拟DOM,当数据改变的时候会生成新的虚拟DOM。同层比对,如果一层不满足要求,之后的也不比对了,后面的就直接废弃掉,整个就替换掉,这样会提升性能。(直观上如果第一层不一样后面都一样,导致后面一样的节点没办法复用,性能很低,怎么会提升性能。实际上是因为同层比对的算法很简单,算法简单带来的是比对的速度会非常快。虽然可能会导致DOM重新渲染上的浪费,但是它大大的减少了两个虚拟DOM之间比对的算法上性能的消耗。) key值要保持相对稳定,在项目中能不用index做key值就不用。 同层比对和key值比对这些比对的方式都是Diff算法的一部分。--性能优化
ref
如果ref和setState一起使用的时候,要注意setState是异步函数,不会立马执行。有时候会使得无法正确输出页面DOM的真实情况
为了避免这种情况,setState还有第二个参数,它是一个回调函数,执行的时机是在等异步的完全结束了再执行。
生命周期函数
生命周期函数指在某一个时刻组件会自动调用的函数(当state和pros变化的时候render就会被调用,render函数就是生命周期函数) 组件一创建就会被调用 constructor函数和生命周期函数
每一个组件都可以有生命周期函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 shouldComponentUpdate(nextProps,nextState) { if (nextProps.content !==this .props.content) { return true ; }else { return false ; } } 设置shouldComponentUpdate这个声明周期函数提升了组件的性能,性能提升在避免组件做无谓的render操作。render重新执行就意味着react底层需要对组件重新生成一份虚拟DOM,这样就要和之前的虚拟DOM做比对,虽然虚拟DOM的比对比真实DOM的比对性能要快的多,但是能省略这个比对过程,当然可以节约更多的性能。--性能优化 一般把ajax请求放到ComponentDidMount()来做 如何发送ajax请求,首先我们要借助一个库 在todolist输入命令行:yarn add axios componentDidMount() { axios.get('/api/todolist' ) .then(() => {alert( 'success' )}) .catch(() => {alert( 'error' )}) }
day3 CSS过渡动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 可以使用关键帧 @keyframes identifier { 0 % { opacity: 1 ; color: red; } 50 % { opacity: 0.5 ; color: salmon; } 100 %{ opacity: 0 ; color: blue; } } .hide{ animation: identifier 2 s ease-in forwards; } 注意:关键帧当动画执行结束后,最后一帧动画的CSS效果不会被保存下来。加了forwards属性就可以保存下来了 实际上用CSS3开发出的动画具有局限性,涉及js动画的时候就没法处理了。因此得使用下方的模块
使用react-transition-group实现动画
使用这个可能实现更加复杂的动画效果
[官网文档API网址](React Transition Group (reactcommunity.org) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 App.js文件里 对单个元素实现效果: <CSSTransition>组件 <CSSTransition in ={this .state.show} timeout={1000 } classNames='fade' unmountOnExit onEntered={(el)=>{el.style.color='blue' }} appear={true } > <div>hello</div> </ CSSTransition> style.css文件里 .fade-enter, .fade-appear { opacity: 0 ; } .fade-enter-active , .fade-appear-active{ opacity: 1 ; transition: opaciy 1 s ease-in ; } .fade-enter-done { opacity: 1 ; } 补充:很多相关的API都可以展示效果(例如出场动画第一帧,中间帧等等) 对多个元素实现效果: <TransitionGroup> {this .state.list.map((item,index )=> { return ( <CSSTransition timeout={1000} classNames='fade' unmountOnExit onEntered={(el)=>{el.style.color='blue'}} appear={true} key={index} > <div > {item}</div > </CSSTransition > ) })} </TransitionGroup>
注意 :如果CSSTransition
实现不了的效果就可以去浏览Transition
API。(API位置在官网文档最下方)
redux (数据层框架)
用一张图很好的表示有无Redux之间的区别(数据的传递更加简化,便捷)
有Redux不管组件的层次结构有多深,走的流程都是一样的。组件改变->数据修改->其余组件取用
整体redux流程:react要去改变store里的数据,首先要一个派发action,action会通过dispatch方法传递给store,store再把之前的数据state和action转发给reducers,reducers是一个函数,当reducers接收到state和action之后,做一些处理之后,会返回一个新的state给到store,store用这个新的state替换掉之前store里的数据,store数据发生改变之后,react组件会感知到store发生改变(用的是store.subcribe()
来监测store里状态的改变)这个时候就会从store里面重新取出数据更新组件的内容,页面就发生变化了(this.setState(store.getState())
)。
对于todolist的案例要使点击某项实现删除某项的功能 首先先绑定一个删除的回调函数,派发一个action(某项的索引index会传进去)之后在reducers值删除(由于reduces可以接收state,但是决不能修改state)这里就要对之前的状态做一个深拷贝
1 2 3 4 5 6 7 if (action.type === 'delete_todo_item' ){ const newstate = JSON ,parse(JSON .stringfy(state)); newState.list.splice(action.index,1 ) return newstate } **注意** 讲解简书这个项目的老师和尚硅谷的老师(好像不需要深拷贝,用的另外一个方法)对redux基础知识的讲解有点出处。 之后会做整合的博客
对ActionTypes的拆分:目的是为了拼写错误难以找出错误,有了拆分的文件就拼写错误控制台就会直接定位报错的位置
1 export const CHANGE_INPUT_VALUE='change_input_value'
利用actionCreators对action进行管理:目的提高代码的可维护性,加上前端有自动化测试的工具,如果你把action写在一个文 件里,自动化测试也会非常方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 管理前:handleInputChange(e){ const action = { type:CHANGE_INPUT_VALUE, value:e.target.value }; store.dispatch(action); } 管理后:在actionCreators文件内: export const getInputChangeAction=(value )=> ({ type:CHANGE_INPUT_VALUE, valuee.target.value }) handleInputChange(e){ const action = getInputChangeAction(e.target.value)store.dispatch(action); } **高代码的可维护性 *自动化测试也会非常方便
补充
1 2 3 4 5 6 7 8 1. store是唯一的。2. 只有store能改变自己的内容。3. Reducer必须是纯函数:给定固定的输入,就一定会有固定的输出,而且不会有副作用。4 。rudux核心API -creatStore 帮助创建一个store -store.dispatch 帮助派发action,action会传递给store -store.getState 帮助获取store里的数据内容 -store.subscribe 帮助订阅store里的改变,只要store改变,store.subscribe接收的这个函数里的回调函数就会执行
UI组件和容器组件
UI组件负责页面的渲染,容器组件负责页面的逻辑
无状态组件
当一个组件只有render函数的时候,就可以使用无状态组件去替换这个普通组件
无状态组件相对于普通组件的优势在于:性能比较高,因为无状态组件仅仅是一个函数,普通组件是js的一个类,类生成的对象还有生命周期函数render,执行的东西要远比无状态组件要多得多。
1 2 3 4 5 6 7 8 9 10 11 无状态组件: const 组件名 = (props)=>{ return } 普通组件 class 组件名 extends Component { render(){ return } } 前提:一个组件只有一个render的时候就可以用无状态组件去替换普通组件
Redux中发送异步请求获取数据
通过axios发送请求,将后台返回的数据传给store,但是传之前要做几个流程。首先,创建一个actionCreartors的一个函数initListAction,然后在actionTypes(防止拼写错误的文件)定义一个常量
1 2 3 4 5 6 7 8 9 10 11 axios.get('./list.json' ).then((res )=> { const data = res.data; const action = initListAction(data) store.dispatch(action) }) 然后在reducers文件里写 if (action.type === INIT_LIST_ACTION){ const newstate = JSON ,parse(JSON .stringfy(state)); newState.list = action.data; return newstate }
使用Redux-thunk中间件进行ajax请求发送
这个中间件可以使我们把异步请求或者复杂的逻辑放到action里处理
使用redux-thunk可使得创建actionCreartors定义的函数返回的结果可以是一个函数(之前返回的是一个对象),这样就可以发异步请求。
1 2 3 4 5 6 7 8 9 10 11 12 componentDidMount(){ const action = getTodoList(); store.dispatch(action) } export const getTodoList = () => { return (dispatch ) => { axios.get( '/list.json' ).then((res )=> { const data = res.data; const action = initListAction(data); dispatch(action); ***最终执行的 }) 解释:componentDidMount当组件挂载时执行,store派发函数的action实则是派发给store,但是它先会默认自动执行getTodoList()这个函数,随之就会去执行那个异步action,最终派发的也就是dispatch(action)
day4 redux的标准流程
view在redux之中会派发一个action,action通过store的dispatch方法,分发给store,store接收到action连同之前的state一起传给reducer,reducer返回一个新的数据给store,store拿到新的状态去更新旧的状态。
redux的中间件(redux-saga中间件的使用)
中间件的概念是指action和store之间的中间,指的是redux的中间键(dispatch的升级)因为只有redux有action和store的概念。
redux-sage远比redux-thunk复杂的多,里面内置了很多API
有一点区别的是如果上方store.dispatch(action)
store传递的action是一个函数,store接收到action之后它不仅可以派发到reducer还可以派发到一个sagas.js的文件里,可以在sagas.js文件里做一些异步逻辑的操作。
React-Redux的使用
给组件包裹一个 即可让组件使用store里的数据,是react-redux的核心组件。
通过redux的connect方法,将组件和store做一个连接 export default connect(mapStateToProps,null)(组件)
做连接就有一个规则,规则就在第一个参数mapStateToProps
(翻译过来就是把store里的数据映射给组件,变成组件的props)
1 2 3 4 5 6 const mapStateToProps = (state ) => { return { inputvalue:state.inputvalue list:state.list } }
connect的第二个参数是mapDispatchToProps
(把store的diapatch方法挂载到props)
1 2 3 4 5 6 7 8 9 10 11 12 13 const mapDispatchToProps = (dispatch ) => { return { changeInputvalue(e){ const action={ type:'change_input_value' value:e.target.value } dispatch(action); } } } 上述的代码还需像之前一样创建actiontypes和actionCreaters文件 connect生成返回的结果就是一个容器组件(可以理解成业务逻辑,数据,方法对UI组件进行包装)
day5 开始实战 1、引入reset.css文件
使用reset.css的好处:不同浏览器的内核里面对html,body这样的标签,默认样式得设置是不同的,有可能在某一个浏览器上margin间距值是10,而在另外一个浏览器上这个间距值可能就是8。为了能让我们代码在所有浏览器表现形式都是一致的,首先就得对这些浏览器默认对这些标签的实现进行统一,reset.css就是干这个活,使得这些标签在任何浏览器上的margin padding默认都是一样的。一般在开发项目的时候,首先就要先引入reset.css文件。
2、简书网站头部区块的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 与之前不一样的是,对样式的处理需要创建一个style.js文件(样式写在这个文件里,实际上是一个个的组件,不会影响其他页面的组件样式) 也就是我们这个页面用这个组件时候。这些样式只作用这个组件上面,对其他任何地方都没有影响,有效避免写项目潜在的可能css样式冲突的问题 import styled from 'styled-components' export const HeaderWrapper = styled.div` height:56px; background:red; ` HeaderWrapper是带样式的div标签 用法在header组件里引入后使用 <HeaderWrapper>header</HeaderWrapper> 注意点: 1.在style.js写的样式都要加分号``;``不然样式显示不出来 2.a标签不能设置宽高,必须转换为块元素 3.头部导航条设置padding 不然会导致设置的图标重合
就像这样 因此需要去设置头部导航条
1 2 3 4 5 6 7 8 9 10 width:960 px; height:100 %; margin: 0 auto; +padding-right:70 px; +box-sizing:border-box +号为添加的代码,设置一个内聚变以便使其隔开,但是为了保证盒子宽度不变,还得将盒子设置成border-box 由于搜索框和图标是独立的,因此需要<SearchWrapper></SearchWrapper>标签将其包裹 由于该标签被Nav标签包裹且在Nav标签里的子标签都设置了浮动,宽度失效。如果SearchWrapper不设置浮动,宽度就会继承Nav的宽度 就与搜索框的宽度不一致,因此也得将SearchWrapper设置浮动。
day6 整体交换的流程
创建store文件夹->创建index.js ,reducer.js->编写相应的内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 index.js: import {createStore} from 'redux' import reducer from './reducer' const store = createStore(reducer)export default store;reducer.js: const defaultState = {};export default (state = defaultState,action)=>{ return state; }
之后回到App.js引入
1 2 3 4 5 6 7 8 9 import store from './store/index' import {Provider} from 'react-redux' export default class App extends Component { render () { return ( <Provider store={store}> <Header/> </Provider> }
回到header组件引入connect,,将组件和store做一个连接
1 2 3 4 5 6 7 8 9 10 11 12 import {connect} from 'react-redux' const mapStateToProps=() => { return { } } const mapDispatchToProps=() => { return { } } export default connect(mapStateToProps,mapDispatchToProps)(Header)
由于初始化的数据需要放在reducer.js上 所以将组件里的数据进行转移 —>形成版本1
1 2 3 4 5 6 7 8 9 10 11 12 Header/index.js 将状态转移后删除 constructor (props){ super (props) this .state={ focused:false } } reduer.js const defaultState = { focused:false }
由于header组件里有很多是用到state里的focused,因此得将从state拿改成从props拿, 此时就需要mapState…方法
将store里状态的focused映射到组件的Props里去,因此就可以将下方图中的state替换成props
1 2 3 4 5 6 7 8 9 10 11 handleInputFocus=() => { this .setState({ focused:true }) } handleInputBlur=() => { this .setState({ focused:false }) } 这两个方法之前是为了去改变state里的数据 现在数据都不在state里存放了因此就可以连同方法删掉
之后我们要实现当聚焦的时候要改变focused的值 ->一整套流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 当你聚焦的时候,改变store里的数据,就要去调用store的dispatch方法,就要把方法定义在mapDispatch...里 Focus={this .handleInputFocus} onBlur={this .handleInputBlur} 改成 onFocus={this .props.handleInputFocus} onBlur={this .props.handleInputBlur} 有这个方法之后,首先要改变store里的数据首先要创建一个action const mapDispatchToProps=(dispatch )=> { return { handleInputFocus(){ const action = { type:'search_focus' }; dispatch(action) }, handleInputBlur(){ const action = { type:'search_blur' }; dispatch(action) }}} reducers.js export default (state = defaultState,action)=>{ if (action.type==='search_focus' ){ return { focused:true } } if (action.type==='search_blur' ){ return { focused:false } } return state; }
总结
我们把之前组件里的数据移除了,转移到redux公共存储的框架进行存储。Header组价就变成了一个无状态组件
1 2 3 4 5 无状态组件的写法 const Header = (props )=> { 1. render以下的内容(不包括render) 2. 所有的this 去掉 } 到这形成第2 版
day7
安装redux-devtools-extension工具使得网页可以显示redux组件操作状态的变化
1 2 3 const composeEnhancers = window .__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;const store = createStore(reducer,composeEnhancers())
由于reducers存放数据和数据的处理,随着功能的强大,数据量会越来越大,因此不能都放在reducers里存储。
于是在head文件夹下创建一个store的文件夹,创建一个header下的reducer,将store里的reuders移至header的reducers
全局的reducer作为一个仓库,专门来放置各个组件部分的reducers。 此时功能尚未实现,因为header下的index.js文件mapState…方法用的是全局的reducer,因此得修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const mapStateToProps=(state )=> { return { focused: state.header.focused } } 仓库reducers import {combineReducers} from 'redux' import {reducer as headreducer} from '../common/header/store' const reducer = combineReducers({ header:headreducer }) export default reducer间接引入reducers 目录结构会少两层 因为header下的reduces间接由header下的index.js文件管理了 。引入的时候就方便了
day8
由于现在在header组件下创建了一个头部的reducers,由于在头部组件里改变数据的时候派发action,然后reducer接收action然后去修改数据,之前课程说过action不要写成对象,类型也不要写成字符串。用actionCreate去创建action,同时类型定义成常量。
按这个做下去逻辑太绕了,不易理解。大改文件夹存放的位置
在reducer.js文件里,不能对原始的state做修改,为了防止这一点得引入facebook团队研发出来的一个库:immutable.js
它会帮助我们生成一个immutable对象 (不可改变的)如果state是immutable对象就是不可以被改变,就不会出问题。
immutable这个库提供了一个fromJS方法,可以帮助我们把一个js对象转化为immutable对象,用这个方法把数据对象(js对象)
转化为immutable对象。此时header下的index文件的mapState方法里的数据已经转化为immutable类型的数据(state.header)
如果是immutable类型的数据,再进行state.header.focused这种语法是不合理的了。immutable.js里面调用state.header里的属性不能通过点focused的方式来调用,得通过点get的方法传入focused的属性来获取对应的属性。此时不会报错,但是点击的时候就会报错报错的原因在与当你派发一个action的时候,传入reducers返回的是一个普通对象,普通对象时没有get方法的 为了防止这种情况应该这样写(没修改前与修改前的代码)
总结:借助immutable这个库避免编写改变数据里state的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 未修改前的reducers: import {SEARCH_FOCUS,SEARCH_BLUR} from '../constant' const defaultState = { focused:false } const reducer = (state = defaultState,action )=> { if (action.type===SEARCH_FOCUS){ return { focused:true } } if (action.type===SEARCH_BLUR){ return { focused:false } } return state; } export default reducer修改前的header下的index.js const mapStateToProps=(state )=> { return { focused:state.header.focused } } 修改后的reducers: import {SEARCH_FOCUS,SEARCH_BLUR} from '../constant' import {fromJS} from 'immuatble' const defaultState = fromJS({ focused:false }) const reducer = (state = defaultState,action )=> { if (action.type===SEARCH_FOCUS){ return state.set('focused' ,true ) } if (action.type===SEARCH_BLUR){ return state.set('focused' ,false ) } return state; } export default reducer修改后的header下的index.js const mapStateToProps=(state )=> { return { focused:state.header.get('focused' ) } }
即便是修改后的index.js 由于state是js对象,state.header是immutable对象 所以去调用focused属性的时候,数据获取的行为是不统一的。(不太靠谱)因此我们想让state也成为immutable对象,而不是js对象
state对应的数据是在redux下reducers里的index.js里创建的,因此我们要依赖第三方的模块 redu-immutable
1 2 3 4 import {combineReducers} from 'redux-immutable' 之后回到header下改如下代码即可 focused:state.get('header' ).get('focused' ) 可以简写为 focused:state.getIn(['header' ,'focused' ])
下一阶段(搜索区域的布局) F11退出全屏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <SearchList> <SearchInfoItem>教育</SearchInfoItem> <SearchInfoItem>简书</ SearchInfoItem> <SearchInfoItem>生活</SearchInfoItem> <SearchInfoItem>投稿</ SearchInfoItem> <SearchInfoItem>历史</SearchInfoItem> </ SearchList>由于子元素设置浮动了 父元素的高度无法被撑起所以给父元素设置一个BFC export const SearchList = styled.div` overflow:hidden;//此时只是开启BFC使其高度和宽度不丢失 ` ;之后就是该列表聚焦的时候显示,不聚焦的时候不显示,设置一个函数,给函数接收一个变量show。 如果show为真就返回整个列表,假就返回null 最后再调用一下这个方法 {getListArea(props.focused)} 传入focused的值即可
换一批的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 此时的header是一个无状态组件,之后还要添加很多功能,就会出现函数嵌套函数,就会使Header变得越来越庞大,不好维护 因此还是使用Component组件 将原来props前加一个this 且将显示列表的方法定义在组件里接口 由于列表里的内容实际上是通过ajax数据获取的,因此header里不进要有focused的数据还得有列表的内容 因此在header的reducer里加一个list:[]; 当导航的input框做了聚焦之后,就要获取ajax数据,也就是onFocus的时候,onFocus执行的是handleInputFocus这个方法 找到这个方法在方法里获取ajax数据,之前的课程有说过,获取ajax数据一般来说不会直接写在组件里,会把异步的操作放到action,或者放到redux-saga里面进行处理,这里统一使用redux-thunk,把异步操作都放至action里处理 redux-thunk其实就是action和store之间的中间件 其实就是对dispatch方法的升级 因此得写在创建store之前被使用 import {createStore,compose,applyMiddleware} from 'redux' import thunk from 'redux-thunk' import reducer from './reducers' const composeEnhancers = window .__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;const store = createStore(reducer,composeEnhancers( applyMiddleware(thunk) )) export default store;有了redux-thunk之后,就可以去action里面做异步操作了 dispatch(getList());去派发这样一个action 以前的action返回的是一个对象,现在可以返回函数 函数里写个输出123 只要聚焦控制台就会显示123 发请求前要先安装包axios axios.get('/api/headerList.json' ).then((res )=> { }).catch(() => { console .log('ERROR' ); }) } 在public文件夹下创建一个api文件夹之后在创建headerList.json的文件输入一下数据 之后在网页访问这个链接页面就可以显示数据 原理:首先先到功能目录下看看有没有对应的路由,找不到就会去public目录下找api的headerList.json,找到后就会把文件的内容输出出来 通过这个特性,我们可以创建一些假数据,保存。 模拟的数据要和后端对的数据要保持一直,就是定一下格式 public下的api下的header.json添加模拟的数据 此时: axios.get('/api/headerList.json' ).then((res )=> { console .log(res }).catch(() => { console .log('ERROR' ); }) }
之前header下的reducer里定义了一个list:[],现在发送了一个ajax请求,获取到了结果,现在就是希望能用获取的结果替换那个空数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 action.js文件里 export const getList = () => { return (dispatch )=> { axios.get('/api/headerList.json' ).then((res )=> { const data = res.data; const action = changeList(data.data); dispatch(action) }).catch(() => { console .log('ERROR' ); })}} reducer.js文件里 if (action.type===CHANGELIST){ return state.set('list' ,action.data) return state 这样写实际上还是有问题的:创建store,默认list是一个空数组,fromJS方法会将js对象转换为immuable对象,list是个数组,也会变成immuable的数组,但是调用set 去改变list的时候,action.data实际上是一个普通的数组, list数组会由immuable数组变成一个普通的数组,这样的话数据类型就会变了,解决这个问题 export const changeList = (data) =>({ type:CHANGELIST, data:fromJS(data) }) 这样转换一下 const action = changeList(data.data); 这里派发的时候数据就会转换成immuable的数据类型。这样reducer里接收到action的数据类型也就是immuable类型了。 做到这里就可以使用redux的插件去查看显示header里面就list的数组了
之后就是将list里的数组逐一放至getListArea的方法里展示
总结
实际上讲的就是项目中用react-thunk发送ajax数据。获取到数据之后,存到store里面,然后在页面上进行显示,有几个关键点
第一个我们把异步获取的逻辑都拆分到actions文件夹下,如果你想拆分到这里来,就要求action返回的对象不能是一个js对象,而是一个函数,如果要返回的是一个函数就必要要用redux-thunk这个中间件,这是第一个重点
第二个当我们获取到数据,想要改变store里的数据的时候,我们要注意redux里的list因为外层包裹了一个fromJS,它会在创建的时候,会把内部的list定义的数组由一个普通的数组转换为immuable类型的数组,但是想直接改变这个状态的数组,就会把之前immuable类型变成普通数组的类型,因为我们就要再action之前传递action之前就要把数据转换为immuable类型的数据,这样数据统一就不会有问题。
页面循环展示数据,就要调用map方法通过对每一项进行循环展示,对对应的内容都展示在页面上 —功能实现
避免无意义的ajax请求发送(上方衔接最后补充的内容)
每点击一次焦点的时候就会发送一次ajax请求。实际上列表中的数据获取一次就可以了
解决问题:给handleInputFocus(list)
传入list 然后输出list可以看出第一次请求的时候list.size是0,之后就是50,
所以控制当size为0的时候才发送请求,之后不等于0的时候就不发生了,这样就避免了每次获取焦点的时候都发一个ajax请求,从而做到性能上的调优。
1 2 3 4 5 6 7 8 handleInputFocus(list){ if (list.size===0 ){ dispatch(getList()); } 或者 (list.size>0 )&&dispatch(getList());意思都一样 dispatch(searchFocus()); }, 样式加一个cursor:pointer 可使鼠标放上去有个小手的标志。
路由
1 2 3 4 5 import {BrowserRouter,Route} from 'react-router-dom' BrowserRouter代表路由 Route代表路由规则 首先先引入路由组件 下载包:npm add react-router-dom **注意** 前两周这个包刚更新过如果按之前的编码规则会使得路由组件无法显示(太坑了官网也不发布,害我整了半天)
把首页组件拆分成一个个小的组件
外层元素:margin-left: -18px;
内层元素:margin-left: 18px;、
这样就可以充分利用左侧栏的宽度,使其保持一直
1 2 3 4 5 6 7 8 9 10 <TopicItem> <img className='topic-pic' src='//upload.jianshu.io/collections/images/261938/man-hands-reading-boy-large.jpg? imageMogr2/auto-orient/strip' alt='niao' /> 社会热点 </TopicItem> 这是一个标题数据,之后我们拿来用的时候需要从redux里取 数据都存在store里,而Topic是home的自组件 而home是APP的子组件 因此Topic是APP的自组件 想使用store里的数据需要connect做连接 定义一个获取状态的方法 mapState,从reducer获取到list给Topic组件遍历即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React, { Component } from 'react' import {TopicWrapper,TopicItem} from '../style' import {connect} from 'react-redux' class Topic extends Component { render() { return ( <TopicWrapper> {this .props.list.map((item )=> { return ( <TopicItem key={item.get('id' )}> <img className='topic-pic' src={item.get('imgUrl' ) } alt="nihao" /> {item.get('title' )} </TopicItem> ) })} </ TopicWrapper> ) } } const mapState =(state ) => ({ list: state.get('home' ).get('topicList' ) }) export default connect(mapState,null )(Topic)
day9
页面的组件,首先闲在reducer的初始状态定义好相应的信息
1 2 3 4 5 6 7 8 9 10 11 articleList:[{ id: 1 , title:'大数据分析可视化平台' , desc:'支持直连MySQL、SQL Server、PostgreSQL、Oracle、SAP HANA、Hive等数据源,也可上传本地Excel/Csv文件或通过API连接数据。可完成多源交互分析' , imgUrl:'https://lupic.cdn.bcebos.com/20200412/3048304592_14_800_600.jpg' },{ id: 2 , title:'大数据分析可视化平台' , desc:'支持直连MySQL、SQL Server、PostgreSQL、Oracle、SAP HANA、Hive等数据源,也可上传本地Excel/Csv文件或通过API连接数据。可完成多源交互分析' , imgUrl:'https://lupic.cdn.bcebos.com/20200412/3048304592_14_800_600.jpg' }],
之后在组件里通过调用connect 里调用因为这个状态是immuable数据类型 因此需要用get方法来引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import React, { Component } from 'react' import {RecommendWrapper,RecommendItem} from '../style' import {connect} from 'react-redux' class Recommend extends Component { render() { return ( <RecommendWrapper> { this .props.list.map((item )=> { return <RecommendItem imgUrl ={item.get( 'imgUrl ')} key ={item.get( 'id ')}/> }) } </RecommendWrapper> ) } } const mapState=(state)=>({ list: state.getIn(['home','recommendList']) }) export default connect(mapState)(Recommend)
首先所有的数据内容都存在前端的代码里,都是写死的,实际做项目的时候,通过接口来获取的,不能写死。
在public下的api定义一个接口home.json。之后在home组件下借助componentDidMount这个声明周期函数来发ajax请求去获取数据,获取了数据,(当组件挂载完毕)。就要将获取的数据取修改store里初试的数据
所以要创建一个action,派发给store,需要store和组件建立连接
修改store里的数据也就是状态 就得使用connect的第二个参数,通过这个函数就可以定义一个方法将action派发给store
因为UI组件不能和store直接通信,只能通过容器组件。派发给store之后store就会派发给reducer。
1 2 3 4 5 case 'change_home_data' : console .log(action); state.set('topicList' ,action.topicList) 由于是多个对象可以用state的一个方法做一个合并 这样就实现了通过ajax异步的形式获取到了首页的内容
上述的操作都是不合适的因为UI组件不应该有太多的业务逻辑,发ajax请求不应该在UI组件里发,放在容器组件里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 修改前 componentDidMount(){ axios.get('./api/home.json' ).then((res )=> { const result = res.data.data; console .log(res); const action={ type:'change_home_data' , topicList:result.topicList, articleList:result.articleList, recommendList:result.recommendList } this .props.changeHomeData(action) }) } } const mapDispatch = (dispatch )=> ({ changeHomeData(action){ dispatch(action); } }) export default connect(null ,mapDispatch)(Home) 第一次修改后: componentDidMount(){ this .props.changeHomeData() }} const mapDispatch = (dispatch )=> ({ changeHomeData(){ axios.get('./api/home.json' ).then((res )=> { const result = res.data.data; const action={ type:'change_home_data' , topicList:result.topicList, articleList:result.articleList, recommendList:result.recommendList } dispatch(action); }) } }) export default connect(null ,mapDispatch)(Home)
再次升级 ajax请求不放在容器组件里 放在action里 通过调用那个函数
1 2 3 4 5 6 7 8 componentDidMount(){ this .props.changeHomeData() }} const mapDispatch = (dispatch )=> ({ changeHomeData(){ dispatch(getHomeInfo()) }});
而后action里代码可以做升级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 action下home.js import axios from 'axios' export const getHomeInfo = () => { return (dispatch )=> { axios.get('./api/home.json' ).then((res )=> { const result = res.data.data; const action={ type:'change_home_data' , topicList:result.topicList, articleList:result.articleList, recommendList:result.recommendList } dispatch(action); }) } } 优化后 import axios from 'axios' const changeHomeData=(result )=> ({ type:'change_home_data' , topicList:result.topicList, articleList:result.articleList, recommendList:result.recommendList }) export const getHomeInfo = () => { return (dispatch )=> { axios.get('./api/home.json' ).then((res )=> { const result = res.data.data; const action = changeHomeData(result) dispatch(action); 或者写成 dispatch(changeHomeData(result)) }) } } 之后让type成常量 <LoadMore onClick={this .props.getMore}>更多文字</LoadMore>*** 不用写()
对于页数的编码,首先先在reducers定义一个初始状态,定义完之后就可以回到List组件里定义mapState方法中的page:state…
定义完之后将UI组件里的LoadMore里的onClick的回调函数设置为箭头函数 传page进去, 下面调用方法的函数也传page
之后发异步action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <LoadMore onClick={()=>this .props.getMore(this .props.page)}>更多文字</LoadMore> </ div> ) } } const mapState =(state )=> ({ list:state.get('home' ).get('articleList' ), page:state.get('home' ).get('articlePage' ) }) const mapDispatch=(dispatch )=> ({ getMore(page){ dispatch(getMoreList(page)) }}) 异步action export const getMoreList=(page )=> { return (dispatch )=> { axios.get('./api/homeList.json?page=' + page).then((res )=> { const result = res.data.data; const action = AddHomeData(result,page+1 ) dispatch(action); }); }} 每一个异步action都有一个同步action const AddHomeData = (list,nextpage )=> ({ type:ADD_HOME_LIST, list:fromJS(list), nextpage }) 之后就回到reducer 去更新原始页面的状态
回到顶部
回到顶部
1 2 3 4 5 handleScrollTop() { window .scrollTo(0 , 0 ); } 之后实现页面再顶部的时候不显示回到顶部这个图标 在底部才实现
然后在home.js下的传入connect的第一个参数
1 2 3 4 5 const mapState =(state )=> ({ showScroll:state.getIn(['home' ,'showScroll' ]) }) 控制这个值的true 和false 来实现是否显示回到顶部的功能 {this .props.showScroll ? <BackTop onClick ={this.handleScrollTop} > 回到顶部</BackTop > :null }
day10
在组件刚挂载的时候绑定一个监听事件来监测滚轮是是否滑动
1 2 3 4 5 6 7 8 9 10 11 12 13 componentDidMount(){ this .props.changeHomeData() this .bindEvents(); } bindEvents(){ window .addEventListener('scroll' ,this .props.changeScrollTopShow) } } 调用的方法: changeScrollTopShow(e){ console .log(e); }
如果不打印事件 换做
1 2 3 4 5 document .documentElement.scrollTop(获取滚动条位置)要获取当前页面的滚动条纵坐标位置 changeScrollTopShow(){ console .log(document .documentElement.scrollTop); } 我也不明白为什么我打印出的是小数
之后将这个值设置大于400的时候显示回到顶部,否则隐藏,因此我们要改reducer里的数据,通过发action进行改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 changeScrollTopShow(){ if (document .documentElement.scrollTop>400 ){ dispatch(toggleTopShow(true )) }else { dispatch(toggleTopShow(false )) } action.js文件 export const toggleTopShow=(show )=> ({ type:TOGGLE_SCROLL_TOP, show }) reducer.js文件 case TOGGLE_SCROLL_TOP: return state.set('showScroll' ,action.show) 功能实现 但是需要注意的是创建组件组件挂载的时候,往window 上绑定的scroll事件的监听的时候,一定要把scroll事件监听在window 上移除 这样组件的事件就不会影响其他的组件 reducer.js代码优化: case CHANGE_HOME_DATA: return state.merge({ topicList:fromJS(action.topicList), articleList:fromJS(action.articleList), recommendList:fromJS(action.recommendList) }) 定义一个方法 const changeHomeData=(state,action )=> { return state.merge({ topicList:fromJS(action.topicList), articleList:fromJS(action.articleList), recommendList:fromJS(action.recommendList) }) } 然后调用: case CHANGE_HOME_DATA: return changeHomeData(state,action) ***修改部分 case ADD_HOME_LIST: return addhomedata(state,action)
首页性能调优
因为首页的index.js都调用了connect的方法,和store做了连接,这就会产生一个问题,只要store发生了改变,那么每一个组件都会被重新渲染,也就是每个函数的render函数会被重新执行,可能有些数据发生改变了,但是那个数据和这个组件一点关系都没有,但是这个组件依然会被重新渲染,导致性能不好。提高组价性能 shouldComponentUpdata,可以在这里做性能优化的代码,判断只有与这个组件相关的数据发生改变才让render函数重新执行(重新渲染),否则return false不让render函数重新执行。
通过这种方式来避免虚拟DOM的比对,提高性能,react也考虑到了这点,如果你在每个特组件都去自己写should….太麻烦了,react内置了新的组件类型,引入PureComponent,区别就是PureComponent内在自己底层实现了should…,这样就不用我们手写收should……做性能优化。之后就是每个UI组件都替换为PureComponent
之所以项目用PureComponent ,是因为项目的数据管理用了immuable.js的框架,它可以保证我们的数据是immuable的,这样PureComponent和immuablejs数据格式的管理相结合,使用PureComponent一点问题都没有,但是如果你在你的项目里面,没有使用immuable.js管理你的数据,那么使用PureComponen有的时候会遇到坑。(偏底层的坑)
实现点击title跳转至详情页面
当我们在react实现页面跳转的时候,我们要用到react-router-dom第三方模块,它的这种跳转是单页应用跳转,所谓单页应用跳转就是做一个单页应用的时候,单页应用指的是,不管怎么做页面的跳转,整个网站只会加载一个html文件,只会加载一次html文件,那么来看使用a标签来做页面的跳转,符不符合单页应用的定义。network里Doc可以看加载的html文件,使用a标签就是会重新加载html文件,会由原来的localhost变为detail的html,增加了一次html请求,比较耗性能,因为借助react-router-dom的时候,完全没必要重新加载页面,做页面的跳转,正确的做法是用Link代替a标签。
注意:link标签要在Router里面否则会报错,在做头部点击简书logo的时候,因为link在head里,而APP组件的Header是在BrowseRouter外部因此违背了这个规则 导致报错
或者是报这个错
day11 详情页面的布局
从reducer里导入初试状态的content时候会导致标签转义,所以页面显示的内容不是该有的内容。
1 2 3 dangerouslySetInnerHTML={{__html : this .props.content} 这样写不会被转义 之后这些数据想在后端被获取而不是通过store里的原始数据取获取。就需要发ajax请求,步骤都是和之前一样
实现点击不同的title显示不同的详情页(匹配id)
在List.js文件夹下 路径下加上一个id 打开浏览器不显示了
原因是在APP.js组件里路由组件匹配的路径是/detail 而不是/detail/1 因此得在路由组件匹配的时候出换一个‘/detail/:id’
获取参数的第一种方法:动态获取参数
首先打开详情页面的代码 在render和return之间先 打印输出log(this.props)//在控制台里可以看出props里match里的params里有id 然后就是在组件挂载时传入id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 action.js export const getDetail=(id )=> { return (dispatch )=> { axios.get('/api/detail.json?id=' + id).then((res )=> { const result=res.data.data; dispatch(changeDetail(result.title,result.content)) }) } } detail下的index.js class Detail extends Component { render() { return ( <DetailWrapper> <Header>{this .props.title}</Header> <Content dangerouslySetInnerHTML={{__html: this.props.content}}/ > </DetailWrapper> ) } componentDidMount(){ const {id} = this.props.param this.props.getDetail(id);/ /通过后端去返回 } } const mapState=(state)=>({ title: state.get('detail').get('title'), content: state.get('detail').get('content') }) const mapDispatch=(dispatch)=>({ getDetail(id){ dispatch(getDetail(id)); } }) 然后传递路由参数出现了问题 是由于使用了react-router-dom v6的版本 下面有解释 export default connect(mapState, mapDispatch)((props)=>( <Detail {...props} param={useParams()} / >))
巨坑由于使用了react-router-dom v6的版本 使得组件props参数里没有location history等属性。如果使用v5的话就完全避免了这个问题(使用v5下载包的时候附上版本号yarn add react-router-dom@5.3.0)
登陆组件
创建reducers 定义初始状态,login:false 之后在去头部的map方法里那login组件里的login
1 2 3 4 5 login:state.getIn(['login' ,'login' ]) { login ? <NavItem className ='right' > 退出</NavItem > :<NavItem className ='right' > 登陆</NavItem > } 就可以在页面的头部看到是否处于登陆状态还是退出状态
接下来如果用户没有登陆不是显示这个字样 而是跳转到登陆页面
1 2 3 4 5 6 7 8 9 10 11 12 13 <Link to='/login' ><NavItem className ='right' > 登陆</NavItem > </Link> 回到登陆组件 <Button onClick={() => this.props.login(this.account, this.password)}>登录</ Button>因为登陆需要调用ajax接口,把账号密码传递过去的。这时候需要把它写进mapDispatch里 因为要发异步的请求,异步action 只有它才能派发异步action <Input placeholder="账号" ref={(input) => {this .account = input}} /> <Input placeholder="密码" type="password" ref={(input) => {this .password = input}} /> 这两句话的意思就是:通过ref把这两个DOM分别存在this .account和this .password里 这样在登陆的时候就可以调用箭头函数把account和password传进去 之后就是发ajax请求 action reducers 常见的套路 实现的结果就是输入账号密码之后头部的登陆变成了退出 现在就是要实现点击登陆跳转到首页 重定向到首页 又是版本的问题,之前的重定向Redirect也不能用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const {loginState} = this .props; if (!loginState){ return ( <LoginWrapper> <LoginBox> <Input placeholder="账号" ref={(input) => {this .account = input}} /> <Input placeholder="密码" type="password" ref={(input) => {this .password = input}} /> <Button onClick={() => this .props.login(this .account, this .password)}>登录</Button> </ LoginBox> </LoginWrapper> ) }else{ return < Navigate to='/ ' /> } 之后做退出的功能回到header组件的index.js下
1 2 3 4 5 6 7 8 { login ? <NavItem className='right' >退出</NavItem>: <Link to='/ login'><NavItem className=' right'>登陆</NavItem></Link> } 在这里添加一个点击事件onClick={logout}通过调用该回调函数去修改login的值,所以要去派发action 因为这是在header.js下的 要引用login的action 修改数据得到的结果也就是头部的显示结果 就是点击退出的时候上面的退出会改成登陆
创建写文章(只有在登陆状态下才能写)
对于一个组件的实现流程:首先建立writer文件夹下的index.js 把login复制过来,沿用它的一些逻辑
如果登陆了就写文章,如果没有登陆就跳转到登陆见面 然后把这个组件放到APP.js里面 。
同时在头部组件里的一个写文章外部加一个Link 跳转到Writer组件里之后就不做了
代码优化
在每个方法后面加分号;
在action里发异步请求的时候都默认发成功的请求,在企业级项目都有错误的情况.catch()异常逻辑进行补充
异步组件
首先点开network组件 JS刷新页面, 跳转页面,登陆页面,都是加载bundle.js,存在一个问题:所有页面对应的组件代码,都是bundle.js,现在访问的是首页,实际上登陆和详情页面的代码一起都加载了,这样首页加载的速度就有点慢。解决问题:当你访问首页的时候,你只加载首页的代码,访问详情页的时候再去加载详情页的代码 使用异步组件来实现,底层比较复杂,但是使用封装起来的第三方模块会非常简单。
1 2 3 4 5 6 7 8 9 10 11 12 npm add react-loadable 异步组件的时候会有一个问题如果是v5版本的话 detail下的index.js是有用到params参数的,以前可以直接获取是因为 因为APP.js里以前引入的Detail组件,路由对应的组件就是这个组件import Detail from './pages/detail' ; <Route path='/detail/:id' exact component={Detail}></Route> 如果是这样detail可以直接拿到路由信息 但是现在不一样了引入的是import Detail from './ pages/detail/loadable.js'; 所以loadable.js可以获得所有的路由信息,但是它下面的组件获取不了路由内容,因为它不是Router直接对应的组件 箭头所示的就是下面的组件 解决方法就是在index.js组件里加一个withRouter **这是v5的 v6不需要以上操作 调用该方法让Detail组件有能力获取到Router里所以的参数和内容 export default connect(mapState, mapDispatch)(withRouter(Detail));
注意 :使用jsx语法要引入React
day12 补充(之前漏看的视频)
首先在header组件的初始状态定义一个page和totalpage。page的作用是通过页码对内容进行控制,totalpage指的是整体的数据一共有多少页。 通过点击输入框就可以将获取列表,在列表里的action获取到数据后在同步action里面定义总共的页码。
之后在把页面传给reducers,打开reudx点击搜索就可以看到如下获取的列表和总共的页码
1 之后在map方法里获取页码 `` page:state.getIn(['header' ,'page' ]),``
之后在map方法里获取页码 page:state.getIn(['header','page']),
让它一页显示10个
1 2 3 4 5 6 7 8 9 const {focused,list,page}=this .propsconst pageList=[];const Listpage = list.toJS(); for (let i=(page-1 )*10 ;i<page*10 ;i++){ pageList.push( <SearchInfoItem key={Listpage[i]}>{Listpage[i]}</SearchInfoItem> ) }
之后点击搜索显示的就是10个内容 但是才存在一个问题,点击换一换的时候input框会失焦,一失焦下面的区块就隐藏掉了,什么时候隐藏,其实并不是靠这个input的focus来控制的。实际点击区块的白色部分的时候,已经失焦但是区块不会隐藏,把鼠标移出去的时候才会隐藏。因此下面区块的展示和隐藏并不是完全由focus的状态来决定的还由一个mouseleft和mousemove决定的。
因此再加一个属性 mouseIn:false,,因为一开始鼠标就是没进入这个区块,鼠标移入进去,变成true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <SearchInfo onMouseEnter={this .props.handleMouseEnter} 改变mouseIn:false ,这个的状态 同理再加一个 onMouseLeave={this .props.handleMouseLeave}这个方法 一直我们是通过focus来控制显示还是隐藏,其实现在focus不管用,还得借助mouseIn这个变量一起来控制,因此要从mapstate拿到mouseIn if (focused||mouseIn)如果mouseIn为true 也显示下面就是点击换一换 下面的内容也要跟着变 定义方法onClick={this .props.handleChangePage} handleChangePage(){ dispatch(changePage()) }, 之后就是修改page的状态由于状态修改page不能直接加1 因此还要获取totalPage的状态 handleChangePage(page,totalPage){ if (page<totalPage){ dispatch(changePage(page+1 )) }else { dispatch(changePage(1 )) } 把这个函数设为箭头函数之后把page和totalpage传进去之后进行判断 之后去修改状态即可 但是还是会报错原因就是输出for 循环的时候输出的是undefined 。原因是因为当你刚进入页面的时候,header就会被渲染,初始顺序就是list是个空,page是个1 , 然后for 循环一定会执行,所以会循环1 到9 但是list是一个空数组,那么空数组list[0 ]都是undefined 那么key就是undefined 。 因此外层要包裹一个if 判断 if (pageList.length){ for (let i=(page-1 )*10 ;i<page*10 ;i++){ pageList.push( <SearchInfoItem key={Listpage[i]}>{Listpage[i]}</SearchInfoItem> ) } }
之后点换一换的时候让i标签的动画动一下
1 2 3 4 5 6 7 <SearchInfoSwitch onClick={()=>handleChangePage(page,totalPage,this .spinIcon)} > <i ref={(icon)=>{this.spinIcon = icon}} className="iconfont spin"></i> 换一批 </SearchInfoSwitch>
之前换一批图标功能未实现,且iconfont图标在下侧,原因是因为之前设置的iconfont样式覆盖了它
只要把之前的iconfont样式改成zoom即可
当你点击换一批的时候,我们希望i标签的值发生变化, 有一个ref的内容可以回去i标签真实的DOM节点
之后把相应的值传进,之后在方法下方输出spin 控制台就会输出
就获取到spin对应的DOM了
1 2 3 4 5 6 7 8 9 10 11 spin.style.transform='rotate(360deg)' ; 可以获取上一次旋转的角度之后每一次加360 度 下一次就是720 度 使用单纯CSS transition动画效果来实现 let originAngle = spin.style.transform.replace(/[^0-9]/ig ,'' ) if (originAngle){ originAngle=parseInt (originAngle,10 ) } else { originAngle=0 ; } spin.style.transform='rotate(' +(originAngle+ 360 )+'deg)' react是面向数据编程,最难的是在redux里如何对数据进设计
1、react是面向数据编程,最难的是在redux里的数如何的被设计
2、改变它里面的数据,遵循redux里单向数据流的流程,首先先派发action给store,store再给reducers,reducers返回一个新的数组再给store,store去变更自己的内容,当数据发生变更,页面就自动发生变化了。
day13 简书项目上线
首先在后端的htdocs文件夹下创建一个api文件夹,后端的人在api文件夹下的json所各种复杂的运算处理,然后前端就可以将简书项目public文件夹下的api文件夹删除了,因为开发要做接口的模拟,然后在控制台运行npm run build,就会生成一个build文件夹,在build目录下有很多项目需要上线的文件,把build下的所有文件复制粘贴至htdocs文件夹下(和api一个同级)这样就完成了项目的上线。
整体的流程就是将前端的代码拷贝到后端的项目,访问的也就是后端的项目(访问路径就是之前的一个软件XAMPP)。
点start就可以在本地启动一个服务器(启动一个Apache服务器),会启动在localhost:80端口上(可以简写localhost)
完美撒花
react的基础语法
redux数据层框架
react-redux如何方便我们在react之中使用redux
react-router(本课程用的是4.0的版本 而我采用的是6.0天呐)
immuable.js 相关的第三方模块