React实战一
目录
1. 搭建环境
npm install -g creat-react-app # 创建一个项目 create-react-app jianshu cd jianshu npm run start # 安装styled-components 全局统一管理css npm install --save styled-components # 全局样式引入 https://meyerweb.com/eric/tools/css/reset/
background-size: contain;
2. react知识点
1. 组件
组件:就是将整个ui拆分为很多小的,可重用的ui,每一个小的ui独立做自己的事情。
1.1 定义一个组件
一个组件需要继承react.component
重写render()
函数,返回jsx
注意点:
1.只要有jsx的地方必须引入
react
2.返回值中只能有一个元素包裹,
div
包裹整个jsx,如下是错误的返回方式return ( <div> hello world </div> <div> hello world </div> );
import react, { component } from 'react'; class app extends component { render() { return ( <div> hello world </div> ); } } export default app;
1.2 组合与拆分组件
引用官方的一句话:
don’t be afraid to split components into smaller components.
尽可能的让每一个组件都能分工明确,减少重复,加大可重用。例如表格组件,就必须被拆分为一个单独的组件,它可能在各个地方被用到。
组件拆分优点:让开发显得更加清晰明了。
未拆分组件看起来就没有食欲
return ( <div classname="comment"> <div classname="userinfo"> <img classname="avatar" src={props.author.avatarurl} alt={props.author.name} /> <div classname="userinfo-name"> {props.author.name} </div> </div> <div classname="comment-text"> {props.text} </div> <div classname="comment-date"> {formatdate(props.date)} </div> </div> );
1.案例解析
将首页拆分为header, right,如下定义一个首页组件,在首页组件中分别引入header和right组件,这就是一个简单的组件拆分和组合.
<react.fragment />
是一个虚拟的组件,仅仅是为了包裹其中的元素,并不会出现在页面上
index.js
import react from 'react' import header from './header' import right from './right' class index extends react.component { render() { return ( <react.fragment> <header /> <right /> </react.fragment> ); } } export default index
header.js
import react from 'react' class header extends react.component { render() { return ( <react.fragment> <div>i am header component</div> </react.fragment> ); } } export default header
right.js
import react from 'react' class right extends react.component { render() { return ( <react.fragment> <div>i am right component</div> </react.fragment> ); } } export default right
1.3 组件传值
组件传值分为以下几种情况:1. 父组件传值到子组件;2.子组件向父组件传值;3.隔代组件传值
1. 父传子
注意点: props是只读的
例如index.js
向header.js
组件传值
{/* index.js 传递title属性 */} return ( <react.fragment> <header title="header title"/> <right /> </react.fragment> ); {/*header.js 接受值*/} return ( <react.fragment> <div>{this.props.title}</div> </react.fragment> );
1.4 state
state可以用来存储数据,记性状态管理。
it is private and fully controlled by the component.
render函数执行:组件state和props发生改变,render函数会被执行,当父组件重新渲染时候,子组件render函数也会被重新执行
class index extends react.component { constructor(props) { super(props) this.state = { title: 'header title' } } render() { return ( <react.fragment> <header title={this.state.title}/> <right /> </react.fragment> ); } } export default index
1.5 proptypes
当组件之间传值的时候,接收一方往往需要对参数进行校验,这就是proptypes的作用
import proptypes from 'prop-types' class reactui extends react.component { render() { return ( ) } } //属性类型 reactui.proptypes = { //表示name必须是string类型 name: proptypes.string //属性必传 id: proptypes.element.isrequired }
默认值
the
proptypes
typechecking happens afterdefaultprops
are resolved
class greeting extends react.component { static defaultprops = { name: 'stranger' } render() { return ( <div>hello, {this.props.name}</div> ) } }
1.5 生命周期函数
在页面加载的过程中,特定时间点自动执行的函数称为生命周期函数。
生命周期的四个阶段:
- initialization
这一阶段主要加载props和state
- mounting:页面第一次加载的时候才会执行的挂载
componentwillmount
:在组件即将被挂载到页面的时刻执行
render
:
componentdismount
:在组件挂载完成之后会被执行
使用场景:用于发送ajax请求
- updation:数据发生变化的时候会被执行
- props:
componentwillreceiveprops
:
子组件从父组件接受属性,第一次存在父组件,render函数执行,该函数不会被执行,当父组件render函数重新被执行,就会执行这个函数
shouldcomponentupdate
:组件是否需要被更新,返回boolean值
使用场景:shouldcomponentupdate(nextprops, nextstate);这两个参数来接受即将改变的props和state的值;演示:
shouldcomponentupdate(nextprops, nextstate) { if (nextprops.title !== this.props.title) { //放生改变,则需要重新渲染 return true } else { return false } }
componentwillupdate
: 当组件被更新之前被执行,但是取决于shouldcomponentupdate
的返回结果,
render
componentdidupdate
:组件更新完成之后会被执行
- states:
shouldcomponentupdate
componentwillupdate
componentdidupdate
:
- unmounting
componentwillunmount
: 组件从页面移除的时候会被执行
1.6 无状态组件
无状态组件: 就是组件中只含有render()函数的组件,
import react from 'react' class header extends react.component { render() { return ( <react.fragment> <div>xx {this.props.title}</div> </react.fragment> ); } } export default header
这种组件可以被简化为无状态组件
export default (props, context) => { return ( <react.fragment> <div>xx {props.title}</div> </react.fragment> ); }
1.7 list and key
案例:使用list集合渲染一个页面
key的作用:
keys help react identify which items have changed, are added, or are removed. keys should be given to the elements inside the array to give the elements a stable identity
这个主要和虚拟dom有关,可以提高虚拟dom性能。
兄弟姐妹之间的key必须独一无二
import react from 'react' class event extends react.component { constructor(props) { super(props) this.state = { data: [1, 2, 3, 4, 5, 6, 7] } this.handclick = this.handclick.bind(this); } render() { return ( <div> <ul> { this.state.data.map((item) => ( <li key={item}>{item}</li> )) } </ul> </div> ) } } export default event
2. jsx
jsx
既不是string也不是html,是react独有的一种语法,jsx中需要注意的点:
- 标签的类
class
如:<div class='show'></div>
会和es6关键字冲突会使用classname代替 -
<label></label>
for属性也会使用htmlfor
代替
3. 虚拟dom
虚拟dom就是一个js对象,用来描述真实dom
jsx转换成js对象
<div classname='show'>hello world</div> react.createelement('div',{classname: 'show'}, 'hello world');
流程讲解
- state数据
- jsx模板
- 数据 + 模板生成虚拟dom
- 使用虚拟dom来来生成真实dom, 显示在页面上
- state发生改变
- 数据 + 模板生成新的虚拟dom
- 比较原始的虚拟dom和新的虚拟dom之间的区别,找到不同之处
- 操作dom,改变不同的地方
diff算法
上面的第七步骤是如何比对不同之处呢,这里使用了diff算法。
同层比对:第一层有差异,则不会再进行比对,直接向下全部渲染,如果第一层相同,则向下继续比较。
根据key做关联来进行比对: 同层的key必须固定不变,这样才能再进行比对的时候准确的找到原始的dom节点。假如使用index作为key值,当删除一个元素,那个被删除元素后面的所有标签key值都会被改变,那么进行比对的时候,就会出现性能消耗。
5. 函数绑定
react events are named using camelcase, rather than lowercase.
例如
onclick
,onblur
...
import react from 'react' import './event.css' class event extends react.component { constructor(props) { super(props) this.state = { isshow: true } } render() { return ( <div> <button onclick={this.handclick.bind(this)}>切换</button> <div classname={this.state.isshow ? 'show':'hidden'}>你好</div> </div> ) } handclick() { this.setstate((state) => ({ isshow: !state.isshow })) } } export default event
传递参数
render() { return ( <div> <button onclick={() => this.handclick(this.state.isshow)}>切换</button> <div classname={this.state.isshow ? 'show':'hidden'}>你好</div> </div> ) } handclick(isshow) { this.setstate((state) => ({ isshow: !isshow })) }
传递参数的几种方式
<button onclick={(e) => this.deleterow(id, e)}>delete row</button> <button onclick={this.deleterow.bind(this, id)}>delete row</button> <searchswitch onclick={() => switchitem(page, totalpage)}>换一批</searchswitch>
3. redux
# 安装 npm install --save redux
1. 了解是三个概念
actions
: 就像一个快递,里面包含地址(行为),以及物品(数据)
reducers
:就像一个快递员,负责分发不同的快递,根据地址(行为)派送到不同地方
store
: 就像一个总站,可以存储这些快递,最后返回给用户。
与物流之间的区别就是:store不可变,只可以返回数据,而原先的数据一直保留在store里面
1.1 演示
第一步:创建store index.js
这里借助redux createstore方法创建store,并且引入reducer
import { createstore } from 'redux' import reducer from './reducers' const store = createstore( reducer, // 这个可以使用chrome redux 插件进行调试 window.__redux_devtools_extension__ && window.__redux_devtools_extension__() ) export default store
第二步:创建一个reducer
reducer 里面保存数据的初始状态,
defaultstate
解析action的行为
const defaultstate = { list: ['hello', 'world'] } const reducer = (state = defaultstate, action) => { switch (action.type) { case 'add_list_action': return {list: action.data} default: return state } } export default reducer
第三步:使用redux
- this.state = store.getstate() 从store中获取数据
- store.subscribe(this.handsubscribe)订阅数据,监听数据的变化
handclick()
方法中主要定义action,然后利用store进行分发
import react from 'react' import store from './store' class reduxui extends react.component { constructor(props) { super(props) this.state = store.getstate() this.handclick = this.handclick.bind(this) this.handsubscribe = this.handsubscribe.bind(this) store.subscribe(this.handsubscribe) } render() { return ( <div> <button onclick={this.handclick}>点击我</button> <ul> { this.state.list.map((item) => { return <li key={item}>{item}</li> }) } </ul> </div> ) } handclick() { //创建一个action const addlistaction = () => ({ type: 'add_list_action', data: ['1', '2', '3'] }) //这是一个异步操作 store.dispatch(addlistaction()) } handsubscribe() { this.setstate(store.getstate()) } } export default reduxui
2. react-redux中间件
改造1>1.1 中的代码
nmp install --save react-redux
新增加一个父组件,统一管理store,
这个组件拥有store,
<provider store={store}>
表示改下面的所有元素都可以使用store
中的数据
import react, { component } from 'react'; import store from './pages/redux/store' import { provider } from 'react-redux' import reduxui from './pages/redux/reduxui' class app extends component { render() { return ( <provider store={store}> <reduxui/> </provider> ); } } export default app;
步骤三中的修改
第一步:添加
connect
方法第三步:映射state中的属性,定义分发的方法
第四步:连接组件
import react from 'react' import {connect } from 'react-redux' class reduxui extends react.component { render() { //从react-redux中接收store中的信息,以及分发action的方法 const { list, handclick } = this.props return ( <div> <div> <button onclick={handclick}>点击我</button> <ul> { list.map((item) => { return <li key={item}>{item}</li> }) } </ul> </div> </div> ) } } //state就是store中state const mapstatetoprops = (state) => ({ list: state.list }) //这里定义分发action的方法,dispatch 就是store的dispatch方法 const mapdispatchprops = (dispatch) => ({ handclick() { //创建一个action const addlistaction = () => ({ type: 'add_list_action', data: ['1', '2', '3'] }) dispatch(addlistaction()) } }) //连接组件,并且把属性和action行为传递给组件 export default connect(mapstatetoprops, mapdispatchprops)(reduxui)
4. 其他
1. redux-thunk
redux thunk middleware allows you to write action creators that return a function instead of an action
npm install redux-thunk
to enable redux thunk, use
applymiddleware()
这里配合redux-devtools-extension调试工具一起使用
import { createstore, compose, applymiddleware } from 'redux' import reducer from './reducer' import thunk from 'redux-thunk' // 这个就是让调试插件配合中间件使用 const composeenhancers = window.__redux_devtools_extension_compose__ || compose const store = createstore( reducer, composeenhancers(applymiddleware(thunk)) ) export default store
// 定义一个方法,处理鼠标聚焦行为 //然后dispatch分发action handfocus(list) { (list.size === 0) && dispatch(actioncreator.getsearchitems()) dispatch(actioncreator.inputfocusaction()) } //另外一个文件 //=================================== // 这里就是上面的action,这个action是一个方法,在该方法里面发送ajax异步请求, //异步请求之后继续分发另外一个action export const getsearchitems = () => { return (dispatch) => { axios.get("/api/item.json").then((res) => { dispatch(searchitemsaction(res.data.data)) }) } }
正如官方文档所说:redux-thunk允许你分发一个方法类型的action
2. redux-saga
asynchronous things like data fetching and impure things like accessing the browser cache
npm install --save redux-saga
import { createstore, compose, applymiddleware } from 'redux' import reducer from './reducers' import createsagamiddleware from 'redux-saga' import reduxsaga from './reduxsaga' const sagamiddleware = createsagamiddleware() const composeenhancers = window.__redux_devtools_extension_compose__ || compose const store = createstore( reducer, composeenhancers(applymiddleware(sagamiddleware)) ) //运行自己的reduxsaga sagamiddleware.run(reduxsaga) export default store
reduxsaga.js
import { takeevery, put } from 'redux-saga/effects' import axios from 'axios' function* getjson() { const res = yield axios.get('./data.json') yield put({type:"add_list_action", data: res.data}) //继续派发action } function* reduxsaga() { // 当store分发'add_list_action' 这个action的时候,会调用getjson方法 yield takeevery("add_list__pre_action", getjson) } export default reduxsaga
5. immutable
immutable可以保证数据一旦创建,就不会被改变
npm install --save immutable
//目的:将store变成不可变,这是一个简单reducer.js import * as constants from './constant' //es6语法 import { fromjs } from 'immutable' //这里将defaultstore变成一个immutable对象 const defaultstore = fromjs({ focused: false, mousein: false, list: [], page: 1, totalpage: 1 }) export default (state = defaultstore, action) => { switch (action.type) { case constants.input_focused: //这里的set immutable对象的一个值,其实并不是改变了state,而是返回一个新的对象 //通过set可以简化我们的操作,不用重新构建全部的对象 return state.set('focused', true) default: return state } } // 如何获取一个immutable对象呢? const mapstatetoprops = (state) => ({ //这里表示获取header组件下的一个immutable对象 //这里是 redux-immutable 获取数据的形式 // state.header.get('focused') 为immutable获取数据的格式 focused: state.getin(['header', 'focused']), //state.get('header').get('focused') mousein: state.getin(['header', 'mousein']), list: state.getin(['header', 'list']), page: state.getin(['header', 'page']), totalpage: state.getin(['header', 'totalpage']) }) //通过action传递参数的时候也应该是一个immutable对象 export const searchitemsaction = (data) => ({ type: constants.render_list, data: fromjs(data), totalpage: math.ceil(data.length / 10) })
5.1 redux-immutable
redux-immutable
is used to create an equivalent function of reduxcombinereducers
that works with immutable.jsstate.
我们经常面临很多很多组件,但是如果把所有组件的store,reducer, action 维护在一个目录下,那将是惨目忍睹的,所以通常情况下我们会分开管理redux,然后将所有的reducer组合在一起
npm install --save redux-immutable
//import { combinereducers } from 'redux' import { combinereducers } from 'redux-immutable' //可以管理immutale对象 import { reducer as headerreducer} from '../header/store' const reducer = combinereducers({ header: headerreducer }) export default reducer
5. react-router-dom
react router is a collection of navigational components that compose declaratively with your application.
npm install --save react-router-dom
import { browserrouter, route } from "react-router-dom" ... <browserrouter> {/* 表示路由到一个组件 */} <route path="/home" exact component={home} /> </browserrouter>
route
: 表示要路由的到哪一个组件
link
: 表示一个连接,跳转到某个页面
6. react-loadable
一个项目,不同的页面应该按需加载,而不是当首页出来之后加载所有的组件,例如我们访问首页的时候只会加载首页相关的资源,访问详情页的时候加载详情页的代码,这样可以提高首页访问速度以及用户体验
npm install --save react-loadable
案例演示
import react from 'react' import loadable from 'react-loadable' const loadablecomponent = loadable({ //这里`reactui`是一个组件,表示这个组件将会被按需加载 loader: () => import('./reactui'), // 这里是一个函数,可以是一个组件,表示页面加载前的状态 loading: () => <div>正在加载</div> }) //返回无状态组件 export default () => { return <loadablecomponent/> }
7. 在react 使用各种css
1. styled-components
npm install --save styled-components
定义全局样式
import { createglobalstyle } from 'styled-components' export const resetcss = createglobalstyle` body { line-height: 1; } .... ` // 引用全局样式 import { resetcss } from './style' ... render() { return ( <resetcss /> ) }