3/18
环境搭建
- 引入.js文件
- 通过脚手架工具编码,一般有grunt/webpack/gulp帮助我们编写脚手架
- create-react-app 官方提供的脚手架工具 可定制性强
工程目录文件简介
- yarn.lock是我们项目依赖的安装包
- README.md项目说明文件,可以自己用markdown重写。
- package.json 相当于脚手架工具 或者项目的介绍,还有依赖的包,主要是node的包文件,可以让项目变成node包
- .gitignore,里面的文件不会上传到git
- node_modules第三方包文件
- public目录,代码入口index.js,还有之前没有注意到的,js文件里引入css文件。react的设计思路就是all in js
- PWA、manifest.json、自动化测试
3/19
react的代办事件例子
react帮我们完成数据和dom的映射,我们现在就是关心数据就好了,就不需要考虑dom。
?需要复习:this指向问题,比如箭头函数、bind()绑定
react中有一个概念:immutable,state不允许我们做任何改变,我们只能操作副本。不然不利于性能优化。
JSX语法细节补充
Fragment、dangerouslySetInnerhtml、htmlfor、className
拆分组件与组件的传值
父->子:在父组件里为子组件起属性名,在子组件里this.props就可以拿到父组件传过来的。
子->父:子组件不能直接修改父组件的state,但是我们可以结合利用父组件向子组件传值的方法,可以在子组件里调用父组件的方法,向子组件传递方法时,记得bind this指向。
Todolist代码优化
解构赋值把this.props拿出来;map循环可能是一大段js代码,可以封装成一个函数,在jsx里调用;
setState不用再直接填一个对象了,直接用函数return一个对象,或者用括号代替return,但是写成函数的形式时,这个setState就会变成异步的了,需要在setState外额外保存一下需要用到的state,而且这个函数有prevState
,相当于this.state.
key要放在循环的最外层的标签里。
React衍生思考
- 命令式编程:一步一步的去做,一般都是在做dom操作
- 声明式编程:React就是
- 还可以与其他框架并存的,index.html里面操作的不是一个div;
- 组件式开发
- 单向数据流,子组件只能使用父组件传过来的值(read only property),但是不可以修改它,为了测试或者调试方便,不容易留坑。万一哪个state出bug了,又不知道是哪个子组件传的,那就很麻烦了。所以还是父组件方法对应自己的属性,就更方便。
- 视图层框架,只帮忙页面渲染和数据,组件传值交给其他组件。并不是什么都帮我们解决,我们可能需要一些数据层框架里辅助我们,比如mobx、redux
- 函数式编程,利于自动化编程,比如给函数赋值,就可以得到结果
PropTypes与DefaultProps
要先引入,PropTypes是可以对子组件里拿到的props属性做类型校验的。可以加isRequired,还可以arrayof()/oneOfType()来实现多种类型选择。DefaultProps就是默认的值。
props、state与render函数
数据和页面互相联动的机理:当组件的state或者props发生改变的时候,render函数就会重新执行。
当父组件的render被执行的时候,子组件的render也会被执行。
什么是虚拟DOM
JSX模板+数据 生成真实的DOM,如果发生state的变化,就继续替换数据,在此生成真实的DOM,一次次DOM片段的替换,非常消耗性能。但实际上页面可能只有一部分的DOM发生改变,所以我们第二次state的改变时,我们也生成新的DOM,但是并不直接替换原始的DOM,只做比较,找出差异去替换.但是这样,性能提升仍然不明显.因为还是生成真的DOM.
那我们就改成数据+模板生成虚拟DOM,虚拟DOM就是js对象,用它描述真实DOM.如果state发生改变了,然后比较两个虚拟DOM,找到区别,直接操作DOM.
3/20
深入了解虚拟DOM
真实情况是,第一次:得到模板后,*是先生成虚拟DOM,再生成真实DOM。*比如:JSX -> createElement -> 虚拟DOM对象 -> 真实的DOM
return React.createElement('div',{},'item')
优点:
- 性能提升,将DOM的对比换成了JS对象的对比
- 它使得跨端应用得以实现,React Native,就是用react语法写原生应用。因为原生应用(安卓/ios)里没有DOM的,有了虚拟DOM就可以对比了。在原生应用里,虚拟DOM转化为原生的应用组件。在网页应用里,虚拟DOM就转化为真实的DOM。
虚拟DOM的diff算法
react会合并几次setState,所以setState是设计成异步的,为了减少虚拟DOM的比较次数。Diff算法应用于虚拟DOM的比较,比较遵循的是同层比较。比如先对比第一层,如果发现第一次的DOM都不一样了,那就不继往下层比较,而是直接删除下面的,重新生成。用重新生成的DOM替换原始页面的DOM。
算法简单,时间就快了。虚拟DOM的比较也根据key值来比较。所以我们不要简单粗暴地把index作为组件的key值,不然很难保持和新的虚拟DOM的key值保持一致了。比如删掉最前面的组件,后面的都跟着变了,会导致key值不稳定。我们可以试着用item作为key值,也就是内容。。。但是内容万一很多呢?
React中ref的使用
在react里可以用e.target获取事件的元素对应的DOM,也可以用ref。
ref = {(input) => {this.input = input}}
//拿到input标签对应的实际DOM元素
//获取dom
const value = this.input.value
然鹅,如果用它放在setState后来操作DOM,例如,this.ul.querySelector(‘div’).length的个数就总是会少一个。因为setState是异步的。所以react提供了setState的第二个参数,用来当异步执行完后再调用的回调函数。
生命周期
生命周期函数是在某一个时刻会自动调用执行的函数。
react旧版生命周期包含三个过程:
1、挂载过程
constructor()
componentWillMount()
componentDidMount()
2、更新过程
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps,nextState)
componentWillUpdate (nextProps,nextState)
render()
componentDidUpdate(prevProps,prevState)
3、卸载过程
componentWillUnmount()
其具体作用分别为:
1、constructor() :完成了React数据的初始化(props/sate)。
2、componentWillMount() :组件已经完成初始化数据,但是还未渲染DOM时执行的逻辑,主要用于服务端渲染。在组件即将被挂载到页面的时刻自动执行。
3、componentDidMount():组件第一次渲染完成时执行的逻辑,此时DOM节点已经生成了。组件被挂载到页面上被自动执行
4、componentWillReceiveProps(nextProps):接收父组件新的props时,重新渲染组件执行的逻辑。
5、shouldComponentUpdate(nextProps, nextState):在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。在组件更新之前,要求返回一个boolean类型结果。
6、componentWillUpdate(nextProps, nextState):shouldComponentUpdate返回true以后才执行,组件进入重新渲染的流程时执行的逻辑。
7、render():页面渲染执行的逻辑,render函数把jsx编译为函数并生成虚拟dom,然后通过其diff算法比较更新前后的新旧DOM树,并渲染更改后的节点。
8、componentDidUpdate(prevProps, prevState):组件更新完,重新渲染后执行。
9、componentWillUnmount():组件被移出、卸载前执行的逻辑,比如进行“清除组件中所有的setTimeout、setInterval等计时器”或“移除所有组件中的监听器removeEventListener”等操作。
react16.4后使用了新的生命周期,使用getDerivedStateFromProps代替了旧的componentWillReceiveProps(要从父组件接受参数,以及父组件的render函数重新被执行)及componentWillMount。使用getSnapshotBeforeUpdate代替了旧的componentWillUpdate。
利用生命周期提升性能或应用场景
①子组件不跟着父组件无端端被渲染:
shouldComponentUpdate(nextProps,nextState) {
if(nextProps.content !== this.props.content) {
return true;
}
return false;
}
②函数的bind绑定都放在constructor里,这样这些绑定只会执行一次。
③componentDidMount里发生Ajax请求,如果放到render里,就会造成死循环。我只需要发送一次ajax。componentWillMount什么会因为同构应用产生冲突。
使用Charles实现本地数据mock:中间代理服务器,能抓到我们的请求。
React的动画效果
三元运算符的使用,CSS3提供的过渡效果:@keyframes和animation和forwards保持最后一帧的效果。
react-transition-group:第三方库,还能拓展,用js实现动画效果
redux的入门介绍
react只是视图层的轻量级应用,它的数据管理、组件传值不行。我们可以配合数据管理层框架,redux,我们少把数据放到组件里,数据都放在store里管理,一个组件改变了store的内容,其他组件都会感知到并取到新的数据。redux = Reducer + Flux。
组件从store里拿数据,也需要改变数据。组件可以看成是图书馆借书的人,action creator就是你说的要借什么书这句话,这句话传给store,store理解为图书馆管理书的管理员,但她需要记录一下每本书的情况,这个记录本就是reducer。store通过reducer找到书,就返给组件。
组件要改变数据,也先和action说一句话,action就告诉store,store不知道怎么修改,要去查reducer如何改,reducer告诉后,store就去修改,修改好了再告诉组件,我改好了,你来拿好了。
用redux编写todolist
创建store
在store文件夹里创建index.js,通过createStore()创建了store数据公用空间,记得导出去。但这个store是什么都不知道的,所以要顺便把小本本(reducer.js)创建出来,reducer.js里都是函数,接受state(store里上一次存储的)和action,还可以定义defaultState。回到组件页面,store.getState()就可以拿到store的内容了。
?export default 和 export有什么区别?
Action和reducer的编写
比如我们提交按钮的时候,我们需要改变state,我们要说一句话,就是要在提交事件里写一个action对象,这个action是有一定规范的,他有type,有描述,再通过store.dispatch(action)告诉store
store知道了,但是他不知道怎么做,他就带着prevState和action去reducers里找。store很聪明,一旦他接受到action就自动去reducer里找,也就是会自动执行reducer的方法,reducer可以拿到之前存储的store和这次要干的action。reducer根据action.type去判断是做的什么事情。
JSON.parse(JSON.stringfy(state))就是深拷贝。要操作newState,再返回newState给store管理员。也叫reducer能接受state,但不能修改state。
组件页面里,store.subscribe(函数)来进行store数据更新到页面上~也就是订阅store的改变,store改变了就自动执行里头的函数。这样就实现了整个数据的流通更新啦。
actionTypes.js对action的type进行统一管理,这样写错了报错了也不会找不到错误。
使用actionCreator.js来统一管理页面所有的action。也就是把之前组件页面里写的action对象,在这个文件里写好方法,方法直接返回对应的action对象。然后组件页面引入了后直接调用方法就可以了。
3/21
redux知识点复习
- store必须是唯一的。
- 只有store能改变自己的内容。并不是reducer改变,reducer里不能直接写state.xxxx = ssss,他只负责生成新的数据,store拿到newState,他才进行更新。
- reducer必须是纯函数。有固定的输入,就有固定的输出,而且不会有任何副作用。
- redux的核心API:createStore 、store.dispatch 、store.getState、 store.subscribe
UI组件与容器组件的拆分
UI组件一般称为傻瓜组件(负责渲染),容器组件称为聪明组件(负责逻辑)。
当UI组件调用父组件的方法,且要给函数传参时:
onClick = {(index) => {this.props.handleItemDelete(index)}}
因为之前涉及到this指向问题,就是要bind(this,参数)
无状态组件
当一个普通组件只有一个render方法,那他就是一个无状态组件。这个无状态组件就是方法。无状态组件性能可以提高,无需像类一样执行额外的多余的生命周期方法。
const TodoListUI = (props) => {
retunr (
<div></div>
)}
redux中发送异步请求
使用了axios,那怎么从store里拿呢?
首先要去actionCreators.js里创建函数,就是创建一个action对象描述这个异步请求完的事件。然后actionType也要跟进。再返回容器组件调用一下这个方法创建action,再派发给store。所以要接着写reducer
使用Redux-thunk中间件进行ajax请求发送
redux-thunk可以帮助我们把一些异步请求和复杂逻辑放到action里处理。这是redux的中间件,不是react的。
首先要在createStore的页面里引入applyMiddleware,记得会和redux-devTools冲突。然后,我们就可以在actionCreators里编写函数,且是返回函数的函数,以前仅仅只能返回一个描述action的对象,不然dispatch()里会报错,说action只能是对象。!然后如果要改变store里的数据,就又要走redux的流程,这里面也可以disptach!
什么是Redux中间件
redux中间件就是对Dispatch方法的封装和生级。根据参数不同执行不同的流程。
Redux-sage中间件的使用
同样也是异步处理的中间件。可以去GitHub上学习配置。sagas.js里的函数必须是generator函数。takeEvery方法接受action,然后执行自己编写的方法。就是相当于reducer捕获action一样。我们也可以实现异步逻辑拆分到saga.js文件里。然后用put代替store.dispatch,去给到store,store再去查看reducer里换取内容。
React-Redux的使用
第三方模块,帮助我们更好地用redux。
熟悉地组件!用它来包裹起来。提供器连接了store,包裹起来的内部所有组件都能获取store里的内容了。
怎么获取呢? 第二个核心API:connect。它能帮助组件和store做连接。第一个参数是把store的数据映射成Props给组件,所以组件得改写成this.props.xx调用公共数据里写的state,这些state是在reducer里的defaultState里定义的。第二个参数是如果要对store的内容要做修改,把store.dispatch的方法挂载到props上。
connect(mapStateToProps,mapDisPatchToProps)(组件名称)
const mapStateToProps = (state) =>{
return {
inputValue: state.inputValue
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type: 'change',
value:e.target.value
}
dispatch(action)//派发到store,再给reducer
}
}
}
通过connect,可以实现数据变,页面就跟着变化了。connect返回的内容实际上运行导出的结果是一个容器组件,因为它负责逻辑部分,它对UI组件进行包装。
简书PC项目所有命令
create-react-app jianshu
yarn add styled-components
文件夹介绍
- common文件夹:公用组件(header)
- statics文件夹:放置静态资源文件
- index.js是项目入口
injectGlobal问题
injectGlobal这个API
从现在开始废除了,换成 createGlobalStyle
新的API
,作为一个样式组件出现,按照样式组件思想,以一个标签形式被引入。
参考:https://blog.youkuaiyun.com/weixin_42614080/article/details/106037806
图片当background问题
由于webpack打包的时候,background的属性值会当作字符串,所以需要在开头import的形式,然后使用多行文本嵌入变量的形式 background: url(${logoPic}) 去完成效果。
3/27
使用redux-react-dev,会报错:说reducers不是函数,因为也就是说,保证传入createStore的reducer要是一个函数,createStore的第三个参数,enhancer,也要是一个函数。原因是我忘记加()了。compose就是一个包装函数。安装好后工具就变绿了~。
3/31
使用combineReducers
不能把数据都放在reducers,不然太庞大了,管理员(store)会翻看得很慢。可以先找分类,再去找具体操作。分类方法是,在自己得组件里管理数据。比如在自己组件的文件里建立一个store文件夹。同时查看Redux管理工具,看state也帮忙把数据分层了。
然鹅,初步整理好后,动画效果木有了~原因是mapStateToProps里的state要加个header,才能成功取到数据,才能根据focus来加动画效果。
所以,我们把大reducer拆分成许多小reducer,通过combineReducers做整合。而且目录路径记得也整理一下。
使用actionCreators
刚开始我们这样写action的
handleInputBlur() {
const action = {
type: "search_blur",
};
dispatch(action);
},
但是我们不建议把action直接写成对象,或者action的type写成常量比较好。我们在对应的组件里的store文件夹里编写actionCreator.js,在里面定义:不过为啥({})就是定义对象了?
export const searchFocus = () => ({
type: 'search_focus'
})
//组件文件夹
import * as actionCreators from "./store/actionCreator";
handleInputBlur() {
const action = actionCreators.searchBlur()
dispatch(action);
},
还有就是把actionType变成常量,在各个文件夹里做修改~
store文件夹里的index.js都要利用好,做一个出口。
引入immutable.js来管理store中的数据
reducer拿action和原始state做处理,返回新的state,但是我们不能直接修改state,但是immutable.js库就可以帮助我们实现。是Facebook开发的库,帮我们生成immutable的对象,假设state是一个immutable的对象,我们写代码就不会不小心出错。
所以我们一旦把state对象换成immutbale对象,就需要做各种修改,因为普通对象的一些使用方法已经不适用于immutable对象了。
yarn add immutable
//reducer文件
import {fromJS} from 'immutable' //把js对象转化成immutable对象
const defaultState = {
focused: false, //输入框是否被选中
};
//👇👇
const defaultState = fromJS({
focused: false //输入框是否被选中
});
const mapStateToProps = (state) => {
return {
focused: state.header.focused,
};
};
//👇👇使用get方法
focused: state.header.get('focused')
if(action.type === constants.SEARCH_BLUR) {
return {
focused:false
}
}
//👇👇使用set方法
if(action.type === constants.SEARCH_BLUR) {
return state.set('focused',false);
}
immediate对象的set方法
会结合之前的immutable对象的值和设置的值,返回一个全新的对象。
4/1
完善immutable
const mapStateToProps = (state) => {
return {
focused: state.header.get('focused')
};
};
要把这个state也从js对象改成immutable对象,首先看看大reducer对象,这里是定义和整合大state的文件。
yarn add redux-immutable
//交换
import {combineReducers} from 'redux';
import {combineReducers} from 'redux-immutable';
//修改
focused: state.header.get('focused')
focused: state.get('header').get('focused')
//等价于
focused: state.getIn(['header','focused'])
SearchInfo组件开发
这节课老师有讲到怎么查看元素的样式欸~~
Ajax获取请求热门搜索数据
ctrl+D就可以选择所有相同关键字了~
只需要在第一次聚焦输入框时发送Ajax请求,然后在react里保存好数据,后面都不需要再次请求了。其次呢,我们说异步函数不要写在组件里,我们可以借助redux-thunk写在action里头~redux-thunk可以看作是action和store之间的一个中间件,那么我们去到创建store的store.js文件夹里面引入redux-thunk。
在public里建立一个api文件夹,放一些json文件,然后用地址栏去访问是可以访问到这些数据的,是create-react-app的一个惊喜吧。因为它的底层是node服务器,它会现到工程目录找路由,找不到就去public里看看有没有对应的路径,因此我们可以在public写假数据。
隐含难搞的细节:
if(action.type === constants.CHANGE_LIST) {
return state.set('list',action.data);
}
看似正确,实则不正确,因为list是被转为immutable类型的数组,但是传过来的data又是普通数组对象,所以呢,我们需要在写异步函数那里(actionCreator.js)里通过fromJS()做一些处理。
4/3
代码优化
由于switch里的case语句已经弄了return了,所以不需要break了。
实现换一换功能
增添两个页码page和totalPage的数据,来实现换一换的功能。
首先for循环完成page的算法,第一页显示0~9的数据。
for(let i = (page-1)*10; i<page*10;i++){
pageList.push(//html)
}
其次,要让热门搜索的框框点击不失焦。所以不能用focused来控制了,要多加一个属性mouseIn属性,然后用redux的流程去写就好辣~
给onClick={handleChangePage}
传参的时候,需要把它变成箭头函数,在箭头函数里执行,才能带参数。why?
key失效问题
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
因为header组件渲染的时候,已经把for循环给循环十遍了~~不过刚开始循环,list的初始数据是[],所以newList也是空数组,所以每一项都是undefinded。所以需要判断一下newlist是不是空数组,我们只要Ajax请求后的数据。
spin图标的旋转
利用ref节点拿到dom,用dom节点操作style样式,去做spin的图案的旋转。
ref={(icon)=>{this.spinIcon = icon}}
妙啊妙啊,又用到正则表达式,又用js代码操作样式
let originAngle = spin.style.transform.replace(/[^0-9]/ig,'');//让spin图案旋转360度
if(originAngle){
originAngle = parseInt(originAngle,10)
} else {
originAngle = 0;
}
spin.style.transform = 'rotate(' +(originAngle + 360)+ 'deg)'
这次动画效果不用react提供的组件了,直接用css里的transition过渡效果。
很重要一句话:react面向数据编程,最难的地方是reducer的数据如何设计!react要遵循redux单向数据流的流程。
如何规避不必要的Ajax请求
热门搜索的内容只需要聚焦第一次,获取一次就足够了。做法就是把数据list这个state作为参数传递给handleInputFocus方法,你会发现第二次请求list这个对象的size属性会变成50。
(list.size === 0) && dispatch(actionCreators.getList());
4/5
首页开发,路由的使用
yarn add react-router-dom
import { BrowserRouter , Route } from 'react-router-dom'
要是报错说:只能有一个children,那就是在最外层多添加一个div/fragment标签就好了。
?为啥我在reducer.js里都没给reducer起名字,都可以在大reducer里随便起名引入尽力嘚?
!!redux插件里的state真的可以好好看一看,太好玩了!太清晰了!
home组件又开始写reducer
4/7
首页异步请求
在home的index.js里,借助componentDidMount来发送Ajax请求。
在home组件里,我dispatch呢,并不是只有大reducer接受到,只有小reducer才接受到。
然后home组件是个UI组件,里面不适合放请求—》而mapDispatch才是容器组件,可以把逻辑函数部分放入mapDispatch里。----》当然我们用了redux-thunk,应该放在redux-thunk里。所以我们可以进一步在home的store里通过actionCreators创建action。
欸?为啥actionCreators里派发一次action,到了home组件页面又要派发一次action?答案:第一次派发,为了触发actionCreator,第二次派发,为了修改数据。不过我觉得有点点麻烦~如果数据流不多的话,但是这也的逻辑一定要懂。
4/10
加载更多的功能
分析该组件放在哪里,其实它和文章列表是一体的,可以放到文章列表List组件里头~然后为它添加异步点击事件。我们再加一个数据接口,将拿到的数据拼接在数组后。
初步写完异步流程,会报错:× TypeError: item.get is not a function。也就是说,这个数据不是一个immutable数据,所以使用get方法会无效。根据redux调试工具,发现state里的articleList的数据请求得不对,一看,我请求的地址错了。
虽然页面的数据请求成功了,但是打开控制台,还是有错误,因为我们重复请求同一批数据,key值就会因重复报错。暂时用上index去代替。
进阶的是,真实生活和场景中,我们加载更多是利用分页来完成的。我们告诉后端这是第几页,然后后端再根据页码去拿,所以我们可以再请求地址上带上页码的参数:
axios.get("/api/homeList.json?page=" + page).then()
然后,派发action的时候手动给页码加1,并在reducer接受到action,更新state里去,这样就能动态自增页码数辣~
dispatch(addHomeList(result, page + 1))
回到顶部的功能
在index里写了,定义一个state去判断它是否在最顶部了,不是才显示返回顶部,通过监听scroll事件,判断离顶部的距离,然后派发action去修改这个state,组件页面会根据这个state的true/false来选择是否展示返回顶部的组件。在组件被销毁的时候,记得把window这个全局绑定的监听给移出了,remove~~
首页代码调优和路由跳转
我们每个组件都通过connect和store数据池做了连接,所以只要store的数据改变一点点,每个组件的render都会被重新执行,都会被重新渲染。
第一个解决办法就是在每个组件里写shouldComponentUpdate,去判断是否需要更新本组件。由于我用了immutable管理数据,所以可以用上react fiber提供的一个新的内置组件——PureComponent。
页面跳转方面,不得不提的是我们用react写的网页希望是单页面应用,也就是路由跳转的时候只请求一次html文件,像我们之前用的a标签去做链接跳转,每一次跳转都会请求一次html文件,很耗费性能。所以我们用Link代替a。
顺带把首页的图标也改为Link请求,所以要把组件a改为div:
export const Logo = styled.a.attrs({
href: '/'
})`
查看是否有请求HTML文件:控制台-network-docs
突然明白为何要两次派发action了,第一次是在组件里派发action,这个被创建出来的action是去做异步请求的,然后这个action再把请求拿到的数据去做第二次派发,给reducer。我们dispatch action的时候,是所有reducer都能接受到的。
详情页传参
第一种方式是动态路由的方式,然后this.props里的match里就会有params。
<Link to="/detailed">
↓👇↓
<Link to={"/detailed" + item.get("id")}>
<Route
path="/detailed:id"
component={Detail}
exact
/>
componentDidMount() {
this.props.getDetail(this.props.match.params.id);
}
第二种方式是直接在请求地址里放上参数,统样可以在this.props里找到,但是拿到的是?id=1这种形式,需要自己去处理一下。
<Link to={"/detailed?id" + item.get("id")}>
<Route
path="/detailed"
component={Detail}
exact
/>
登录功能
首先把登录页面写好了然后去实现首页检测是否登录。传递两个input的真实dom,这样就可以拿到styled-component组件的value值了~
<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>
退出登录的时候,要在header组件里拿login文件夹下的actionCreators去修改login的值。