一、简述
1. react是什么?
只针对页面的前沿框架,由facebook开发,现如今被国内一线大厂广泛应用
2. 为什么要学react?
原生javascript的缺点:
1. 操作DOM繁琐,效率低(DOM-API操作UI);
2. 直接操作DOM,浏览器会进行大量的重绘重排;
3. 没有组件化编码方案,代码复用率低
React的优点:
1. 采用组件化模式、声明式编码,提高开发效率及组件复用率。
2. 在React Native中可以使用React语法进行移动端开发。
3. 使用虚拟DOM+加优秀的Diffing算法,尽量减少与真实DOM的交互
3. react依赖
1). 依赖文件
各依赖文件说明:
babel.min.js : ES6 ==> ES5
jsx ==> js
react.development.js : react核心库
react-dom.development.js : react扩展库
二、 基本语法
1. jsx语法规则:
1. 定义虚拟DOM时,不要写引号;
2. 标签中混入JS表达式时要用{};
3. 样式的类名指定不要用class,要用className;
4. 内联样式,要用style={{key:value}}的形式去写;
5. 只有一个根标签
6. 标签首字母
(1). 若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2). 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
二、组件
1. 函数式组件:
<script type="text/babel" >
//1.创建函数式组件
function Demo(){
return <h2>我是函数式组件</h2>
}
//渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
2. 类式组件:
3. 组件实例三大属性
1. state :
实例的状态
参数传入状态中,通过get或者setstate得到或者设置
2. props:
props是属性传入,是只读的
规则和默认值:
//定义规则
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
//定义默认值
static defaultProps = {
sex: '不男不女',
age: 18,
}
3. ref
用于标识input等标签的。
ref的三种用法:
1. 字符串形式的ref:(React不推荐)
2. 回调形式的ref:(React推荐,会调用两次render,开发中比较常见)
3. createRef():(React最为推荐)
三、 组件的生命周期
详见下方声明周期api(新与旧)
四、 Diffing算法
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
/*
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
*/
五、react脚手架
1. 创建项目
npx create-react-app my-app
cd my-app
npm start
2.项目目录文件详解:
node_modules: webpack等脚手架工具包
public: 静态资源目录
favicon: web标签图标
index.html : 引用目录下的静态资源(相当于提供一个接口)
manifest.json: 应用加壳(例如:web文件外层加一个安卓的壳就可以作为安卓界面展示)
robots.json : 爬虫规则(哪些资源可以爬,哪些不可以爬)
src :
App.js: 主页面
index.js : 项目入口
reportWebVitals.js : 查看页面性能情况
.gitignore: git的忽略文件格式(git的文件,与react无关)
README.md : git的说明文档(git的文件,与react无关)
package.json : 包的说明文件
yarn.lock : yarn的缓存文件
3.css样式模块化
如果不同模块下存在相同的css,由于最终都会被app.js主页面引用,所以后引用的会覆盖之前引用的css。
解决办法:
在index.css中间加module:
并且按如下方式引用:
4. 功能界面的组件化编码流程
1. 拆分组件: 拆分界面,抽取组件
2. 实现静态组件: 使用组件实现静态效果
3. 实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.3 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
5. 组件之间的参数传递
1. 动态初始化列表,如何确定将数据放在哪个组件的state中?
--某个组件使用:放在其自身的state中
--某些组件使用:放在他们共同的父组件state中(状态提升)
2. 关于父子之间通信:
1. 父组件给子组件传递数据:通过props传递
2. 子组件给父组件传递数据:通过props传递,要求父提前给子传递一个函数
3. 状态在哪里,操作状态的方法就在哪里
六、路由
1. axios发送网络请求
axios.get(`/api1/search/users2?q=${keyWord}`).then(
response => {
//请求成功后通知List更新状态
PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
},
error => {
//请求失败后通知App更新状态
PubSub.publish('atguigu',{isLoading:false,err:error.message})
}
2. fetch发送网络请求
//发送网络请求--使用fetch发送(优化)
try {
const response = await fetch(`/api1/search/users2?q=${keyWord}`)
const data = await response.json()
console.log(data)
PubSub.publish('raxcl',{isLoading:false,users:data.items})
} catch (error) {
console.log('请求出错',error)
PubSub.publish('raxcl',{isLoading:false,err:error.message})
}
3.组件之间通信
//发版消息
PubSub.publish('raxcl',{isFirst:false,isLoading:true})
//订阅消息
this.token = PubSub.subscribe('raxcl',(_,stateObj)=>{
this.setState(stateObj)
})
//取消订阅消息
PubSub.unsubscribe(this.token)
4. 向路由组件传递参数(三种方式)
1. params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数: this.props.match.params
2. search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test/" component={Test}/>
接收参数: this.props.location.search
备注: 获取到的search是urlencoded编码字符串,需要借助querystring解析
3. state参数
路由链接(携带参数):<Link to={{path='/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/" component={Test}/>
接收参数: this.props.location.state
备注:刷新页面也可以保留住传入的参数
5. push模式和replace模式
push:将历史记录进行前进后退,会留下痕迹,可前进后退
replace: 将历史记录进行替换操作,不可前进后退
6. BrowserRouter与HashRouter的区别
1. 底层原理不一样:
BrowserRouter使用的是H5的history API , 不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2. path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如: localhost:3000/#/demo/test
3, 刷新后对state参数的影响
(1). BrowserRouter没有任何影响,因为state保存在history对象中。
(2). HashRouter刷新后会导致路由state参数的丢失!!!
4. 备注: HashRouter可以用于解决一些路径错误相关的问题。
七、React 组件库 (蚂蚁的ant-design)
1. 安装
yarn add antd
八、Redux
1. redux是什么?(管理状态的库)
1 . redux是一个专门用于做状态管理的JS库(不是react插件库)
2. 它可以用在react,angular,vue等项目中,但基本与react配合使用。
3, 作用: 集中式管理react应用中多个组件共享的状态
2. 什么情况下需要使用redux
1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
2. 一个组件需要改变另一个组件的状态
3. 总体原则: 能不用就不用,如果不用比较吃力才考虑使用
3. 安装
yarn add redux
4. redux异步action
1. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回(非必须)
2。 具体编码:
1. yarn add redux-thunk, 并配置在store中
2. 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
3. 异步任务有结果后,分发一个同步的action去真正操作数据。
3. 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action
九、react-redux
1. 基本概念:
1). UI组件:不能使用任何redux的api,只负责页面的呈现、交互
2). 容器组件: 负责和redux通信,将结果交给UI组件
2. 如何创建一个容器组件-----靠react-redux 的connect函数
connect(mapStateToProps,mapDispatchToProps)(UI组件)
-mapStateToProps: 映射状态,返回值是一个对象
-mapDispatchToProps: 映射操作状态的方法,返回值是一个对象
3. 注意点: 容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
十、react扩展
1. setState
setState更新状态的2种写法
(1). setState(stateChange, [callback])------对象式的setState 1.stateChange为状态改变对象(该对象可以体现出状态的更改) 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用 (2). setState(updater, [callback])------函数式的setState 1.updater为返回stateChange对象的函数。 2.updater可以接收到state和props。 4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。 总结: 1.对象式的setState是函数式的setState的简写方式(语法糖) 2.使用原则: (1).如果新状态不依赖于原状态 ===> 使用对象方式 (2).如果新状态依赖于原状态 ===> 使用函数方式 (3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
2. lazyLoad
路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包 const Login = lazy(()=>import('@/pages/Login')) //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面 <Suspense fallback={<h1>loading.....</h1>}> <Switch> <Route path="/xxx" component={Xxxx}/> <Redirect to="/login"/> </Switch> </Suspense>
3. Hooks
1. React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法 (2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
(1). State Hook: React.useState() (2). Effect Hook: React.useEffect() (3). Ref Hook: React.useRef()
3. State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作 (2). 语法: const [xxx, setXxx] = React.useState(initValue) (3). useState()说明: 参数: 第一次初始化指定的值在内部作缓存 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数 (4). setXxx()2种写法: setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值 setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子) (2). React中的副作用操作: 发ajax请求数据获取 设置订阅 / 启动定时器 手动更改真实DOM (3). 语法和说明: useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行 (4). 可以把 useEffect Hook 看做如下三个函数的组合 componentDidMount() componentDidUpdate() componentWillUnmount()
5. Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据 (2). 语法: const refContainer = useRef() (3). 作用:保存标签对象,功能与React.createRef()一样
4. Fragment
使用
<Fragment><Fragment> <></>
作用
可以不用必须有一个真实的DOM根标签了
5. Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
1) 创建Context容器对象: const XxxContext = React.createContext() 2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据: <xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider> 3) 后代组件读取数据: //第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
注意
在应用开发中一般不用context, 一般都用它的封装react插件
6. 组件优化
Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1: 重写shouldComponentUpdate()方法 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false 办法2: 使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true 注意: 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false 不要直接修改state数据, 而是要产生新数据 项目中一般使用PureComponent来优化
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中: 使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A> React中: 使用children props: 通过组件标签体传入结构 使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A> <B>xxxx</B> </A> {this.props.children} 问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A> A组件: {this.props.render(内部state数据)} C组件: 读取A组件传入的数据显示 {this.props.data}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发 static getDerivedStateFromError(error) { console.log(error); // 在render之前触发 // 返回新的state return { hasError: true, }; } componentDidCatch(error, info) { // 统计页面的错误。发送请求发送到后台去 console.log(error, info); }
9. 组件通信方式总结
组件间的关系:
-
父子组件
-
兄弟组件(非嵌套组件)
-
祖孙组件(跨级组件)
几种通信方式:
1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式
比较好的搭配方式:
父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
* 特殊章、buff点技能加成
1.demo()和demo的区别:
demo()是把demo函数的值给onClick(即页面加载时就调用一次demo函数);
demo是指定点击后调用demo方法;
2. 高阶函数和函数柯里化
高阶函数:如果该函数符合如下两个条件的任意一个,那个该函数就是高阶函数。
1. 若A函数,接收的参数是A函数,那么A函数就是高阶函数。
2. 若A函数,调用的返回值依然是一个函数,那么A函数就是高阶函数。
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码格式。
看完这个栗子,是不是理解了多次接收,统一处理的概念了!!!
3. 合并与展开
可以利用region与endregion关键字进行合并与展开
4. 代码片段
-----需要安装ES7插件
1. rcc : 类定义组件
2. rfc : 函数定义组件
** 用过的API
生命周期API
声明周期(旧)
1. 卸载组件 : unmountComponentAtNode
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
2. 组件将要挂载 : componentWillMount()
3. 组件挂载完毕 :componentDidMount() (开发三大常用之一)
//组件挂载完毕
componentDidMount() {
this.timer = setInterval(() => {
//获取原状态
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) opacity = 1
//设置新的透明度
//简写形式,当前后相同时,可简写
this.setState({ opacity })
}, 200);
}
4. 组件即将销毁:componentWillUnmount() (开发三大常用之二)(之三是render)
//组件即将销毁
componentWillUnmount() {
//清楚定时器
clearInterval(this.timer)
}
5. 是否应该更新状态 : shouldComponentUpdate() 不写默认返回true;
//控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}
6. 组件将要更新的钩子: componentWillUpdate()
//组件将要更新的钩子
componentWillUpdate() {
console.log("componentWillUpdate");
return true;
}
7. 组件更新完毕的钩子: componentDidUpdate()
//组件更新完毕的钩子
componentDidUpdate() {
console.log("componentDidUpdate");
return true;
}
8. 强制更新 : forceUpdate()
//强制更新按钮的回调
force = () =>{
this.forceUpdate()
}
9. 子组件将要更新(有一个坑,第二次调用才会更新): componentWillReceiveProps()
声明周期(新)
1. getDerivedStateFromProps(): 根据属性prop得到状态
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
2. getSnapshotBeforeUpdate():更新完成的前一步(旧状态)
//更新完成的前一步(旧状态)
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight
}
其他API
1. 定时器:setInterval
this.timer = setInterval(() => {
//获取原状态
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) opacity = 1
//设置新的透明度
//简写形式,当前后相同时,可简写
this.setState({ opacity })
}, 200);
2. 透明度 :opacity
<h2 style={{ opacity: this.state.opacity }}>你好,我是魅魔!~~~</h2>
3. 滚动条 : overflow: auto
4.list.scrollTop =30 : 列表往上顶30px
5. list.scrollHeight : 内容区的高度(相当于后端list.size())
6. defaultChecked={true} : 默认勾选
<input type="checkbox" defaultChecked={true}/>
7. 鼠标移入移出:
<li style={{backgroundColor:mouse? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
8.确认弹窗:
window.confirm('确定删除吗')
9.按键判断:
//判断按键方法入口
onKeyUp={this.handleKeyUp}
//判断是哪个键
//解构复制获取keyCode,target
const {keyCode,target} = event
//判断是否是回车按键
if(keyCode !==13) return
//添加的todo名字不能为空
if(target.value.trim() === ''){
alert('输入不能为空')
return
}
10. 获取文本框中的值:
target.value.trim()
11. 对数组做处理: reduce
//已完成的个数
const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
12. 实现路由链接高亮效果:NavLink
<NavLink className="list-group-item " to="/about">About</NavLink>
<NavLink className="list-group-item " to="/home">Home</NavLink>
13。 路由开启严格匹配: exact={true} 或者 exact
14. 重定向(当没有匹配的路由时,Redirect给了最后的底牌):<Redirect>
15. 让一般组件具有路由组件所特有的API : withRouter
import React, { Component } from 'react'
import {withRouter} from 'react-route-dom'
class index extends Component {
render() {
return (
<div>
</div>
)
}
}
export default withRouter(Header)
withRouter的返回值是一个新组件