下面是2019/11/26之前的学习记录
React
特点
- 声明式设计
- 组件化设计
- 支持客户端和服务器端渲染
- 高效
- 单向数据流(目前我还真是不理解)
- 面向组件编程
高效的原因
-
不是直接操作dom而是操作虚拟dom
-
dom diff算法(要看)(已经变成Fiber了)
https://blog.csdn.net/weixin_43606158/article/details/89422894
模块和组件的理解
http://www.zijin.net/news/tech/1439215.html
这个文章和我目前理解的模块,组件理解差不多
模块
侧重点应该在于功能性,业务性,有一套完整的布局代码,交互功能,数据处理,数据结构,接口等
组件
强调的是高度复用性,不一定是一个完整的软件组织,可能只是一个单元,而刚好在每个页面里的都会有
总结:感觉模块更要求独立和低耦合性,而组件则是高复用性而已
画个图hhh,模块给我的感觉像下面的感觉
组件就更像下面的感觉
其实感觉模块和组件像是相对的术语,只是说可能根据实际环境的说法可能同一个东西既可以叫模块和组件,但是不是我这么说就是说两者完全没有区别,毕竟两者强调的东西不太一样,所以用词尽量准确,也许在后期的大量工作的经验和见识下应该会逐渐有更加的清晰地认识
PS:组件名首字母必须大写
定义组件的方式:
- 函数式
- es6类的方式
虚拟dom
虚拟dom:是程序员手动模拟实现的
- 更新虚拟dom不会主动导致重绘,只有当渲染执行时才会去更新指定的发生改变的DOM元素,这是因为由于react,导致dom 和虚拟dom产生了一种映射关系
- 虚拟dom更加的轻(属性更少相比真实dom)
DOM Diff算法
用到的模块和Vscode下载组件功能语句含义
es7便捷语句
rcc 迅捷建立类式组件结构
rfc 迅捷建立函数式组件结构
扩展包
classNames 用于动态的控制组件的类名添加
styled-components 将样式组件化,组件化出来的组件可以控制其标签内部的内容样式
prop-types:
- 检测传入属性的数据类型是否按照程序员个人规定的数据类型传入值,错误会报warning
- isRequired将必需属性做上标记,缺失则会警告
- propTypes用于规范传入数据的数据类型,defaultProps用于设置属性默认值(当外部没有这个属性会自动给传入的props添加这个属性键值对)
PS:
- jsx里面写js代码就是加上一个花括号就行了
- return里只能用一个根元素
- 如果不想让子组件的根元素(空的div)出现,可以从react库中导出一个组件叫做Fragment来代替不想要出现的根元素,当然直接写空标签也行(有种欲练神功必先自宫的下句,不自宫也行。。。)
- 组件如果不使用单闭合标签而是双标签的话,其props会多出一个属性叫children,用于存放双标签的内容(前提是要有内容,且任何数据都可以:标签,数字(需要以花括号的方式传递而非双引号,如果是双引号则传递的是字符串),方法etc。。),之间可以插入正常的html标签但是不建议这么做(会有warning),毕竟组件的基本要求就是一个组件做一件事,如果插入HTML标签反而是浪费了react本身的优点。
编写react脚手架目录的一些Good方法
将组件分别放在自己组件名对应的文件夹里面导出
然后在components内部使用一个index.js来导出所有组件
然后外部的index.js就可以一次性全部导入
//下面是内层的index.js的内容
import TodoHeader from './TodoHeader/TodoHeader'
import TodoInput from './TodoInput/TodoInput'
export {
TodoHeader,
TodoInput
}
//下面是最外层index.js
import {
TodoHeader,
TodoInput
} from './components'
就像这样目录结构会比较方便,如果是组件内部还要导入子组件,就建立在本组件的文件夹下直接导入即可
函数式组件和类组件传递参数的区别
区分方式 | 类组件 | 函数式组件 |
---|---|---|
状态 | 有state(有状态组件) | 没有state(无状态组件) |
受控 | 不受控、半受控组件 | 完全受控组件 |
Ps:尽可能使用函数式组件,毕竟是完全受控组件,消耗的性能会比类组件低很多,如果不需要做状态处理的时候,尽可能使用函数式组件
11-27
props
- 用于存放传入属性(以键值对的方式)的对象,准确的来说是用于存放父组件向子组件传递的数据。
- propTypes规范属性数据类型,defaultProps设置属性默认值
- 特别地props.children会存放该传递参数组件内的所有子节点,并且无节点时返回undefined,单节点返回object,多节点返回array
state
用于存放组件的数据的位置
props&state
当state改变的时候,若下面的穿入参数是以对象的方式传入则使用es6的扩展运算符即可
setState
react里只允许这种更改数据后进行重新渲染
其次他是一个异步执行函数
第一个参数有两种情况:
-
传入一个对象
this.setState({y:!y})
-
方法
Click = () =>{ console.log(this.state.y) this.setState((prevState)=>{ return { y: !prevState.y } },()=>{ console.log(this.state.y) }) } //prevState是上一次状态的state,名字自定义不过为了见名知意
第二个参数:由于setState是异步的,如果想要获取到最新的state,应该在这个回调里来获取(虽然说是异步函数实际上只是模拟异步,并非真的异步)
也就是说上面的函数执行之后,在函数里的state是还未变化的state,而setState第二个参数里的异步函数里的打印的state才是更新之后的state
事件
和原生事件不同的是,命名要小驼峰式
类组件的声明方法格式
如果是按照传统的方式命名类里的方法,就会出现this指向的问题,三种解决方式
-
将外部this存储在一个变量
that
中,函数内部写指向时改为that.setState({...})
-
使用
bind,apply,call
方法绑定 -
使用箭头函数
handleClick = () =>{ this.setState({ inputValue: e.currentTarget.value }) }
其实es6语法中提到的箭头函数就很好的解决this在特殊情况下需要改变当前执行环境对象
传入参数的方式
- props
- redux
- 父组件通过props向子组件传入一个方法,子组件在通过调用该方法,将数据以参数的形式传给父组件,父组件可以在该方法中对传入的数据进行处理
- 路由传值(暂时没有学到这儿)
向下层传递的方法
一般来说是props向下传递,或者render函数里渲染的对应组件添加属性之后去用bind传入参数。(后者相当不推荐,毕竟每次渲染就会重新绑定一次,浪费性能)
最好的方式是使用props,当然函数也可以传下去,在下层再调用,传入下层的好处就是上层的函数可以使用下层的数据(发现的比较有意思的一个用法)
然后写的过程发现主页面我写了两个类组件,方法在不是直接渲染的那个类组件了,就思考到怎么把子组件的数据向上层传递
向上层传递的方法
- 网上查阅了应该是使用redux,这个等有空了再写
- 父组件通过props向子组件传入一个方法,子组件在通过调用该方法,将数据以参数的形式传给父组件,父组件可以在该方法中对传入的数据进行处理
例子
//子组件
var Child = React.createClass({
render: function(){
return (
<div>
请输入邮箱:<input onChange={this.props.handleEmail}/>
</div>
)
}
});
//父组件,此处通过event.target.value获取子组件的值
var Parent = React.createClass({
getInitialState: function(){
return {
email: ''
}
},
handleEmail: function(event){
this.setState({email: event.target.value});
},
render: function(){
return (
<div>
<div>用户邮箱:{this.state.email}</div>
<Child name="email" handleEmail={this.handleEmail.bind(this)}/>
</div>
)
}
});
//父组件
import Child from './Child.js';
export default class Parent extend compenent{
getData=(data)=>{
console.log(data);
}
render(){
return (
<div>
父组件
<Child getData={this.getData}/>
</div>
)
}
}
//子组件
export default class Child extend compenent{
state={
data:[1,2,3]
}
render(){
const {data}=this.state;
return (
<div>
子组件
<button onClick={()=>{this.props.getData(data)}}><button>
</div>
)
}
}
CSS Module
这个是react-create-app本身自带的功能
使用它的好处在于在组件很多的时候可以防止变量污染
也就是说这个模块会使css文件产生局部作用域,避免常规css的变量重名,同时也便于管理,除开react本身将HTML,JS以组建的方式分开管理,CSS Module也加入了这方面,三大支柱都完全的以组件的方式分开保管,开发和维护得到了很大提升。
dangerouslySetInnerHTML
这个方法可以使得state里的HTML标签字符串以HTML的方式去渲染,不过不提倡使用,不当的使用会提供XSS攻击的机会
//这是constructor的部分
this.state = {
content:'<h1>标题名称</h1>'
}
//这是render函数里的写法
render(){
return(
<div>
<div dangerouslySetInnerHTML={{__html: this.state.content}} />
{/* dangerouslySetInnerHTML是固定的用法,遵循上面的例子 */}
</div>
)
}
12-6
react的一些命名规范和思维
- 小驼峰式用于描述方法
- 小驼峰式用于描述方法
- 写一个函数之前最好先做判空处理
- 对于props传递的数据最好是在子组件里使用propTypes去要求和检查对应的数据
- 组件化要分的更加明确,ajax请求分在另一个文件夹里去存储
- (即将要学习的)数据除非特定情况使用state,一般最好是同一数据源仓库管理(用redux??)组件和数据都统一一个源头,便于管理,同时也是react的单向数据流的思维(后来和老大沟通也说到,也不是说一定来说要这么去使用,灵活的去使用就好)
生命周期
生命周期图表
目前的认识,意思是指将组件渲染过程中一些特殊的方法挂载在固定的运行阶段
通过固定的生命周期运行指定的方法从而降低这些指定方法重复的渲染,去提高性能降低内存消耗,考虑到react的算法机制并不是一个个去查找具体变化的元素,而是发现更新的时候直接去替换,或者说删除再新增,所以当一个子组件里存在很多元素的时候,不使用生命周期去固定渲染时期有可能一个子组件下面的其中一个元素更新就会导致整个子组件重新渲染浪费了不必要的内存
举个例子,比如说当子组件下的两个单选框有一个的勾选状态发生变化的时候就会导致两个单选框都被重新渲染,这个时候就需要使用componentDidMount方法去根据对应的单选框的某一个属性监听去做判断来执行componentDidMount方法,就能防止有一个的勾选状态发生变化的时候就会导致两个单选框都被重新渲染的情况
还有ajax这类方法或者请求的发起时间也可以放在componentDidMount方法中等等,剩余的其他方法还需要再了解,后续再补充吧
React hooks函数
说这个函数之前,先说一说,受控组件,半受控组件和不受控组件,在我的理解来说受控组件指的是函数式组件,类组件则是满足剩余的条件
在编写界面的时候,尽可能的使用函数式组件,它是属于完全静态组件,只接受上级组件的props,没有state&setState,就大大的降低了数据流动,能够节省出更多内存共其他组件使用,但是当面临大型项目开发的时候难免后期会对项目进行再开发或者维护,当你需要对它进行state操作的时候,就必须更改成class组件,不过hooks的出现很好的解决掉了这个问题,它使得函数式组件也可以进行state和setState的操作
useState
这里具体是说useState这个钩子函数它的第一个参数就类似于state,第二个就类似于setState,注意是类似哦,当你使用import 导入useState之后就可以方便的向函数式组件进行像class组件的state,setState的操作了
useEffect
这个钩子函数更类似于componentDidMount
&componentDidUpdate
两者结合的效果,也就是说当组件发生挂载和更新的时候就会触发useEffect这个钩子函数的效果
关于hooks的一些理解
最好是数据操作不多的时候对函数式组件进行这些操作,才满足了我们的初衷也就是尽可能的降低不必要的消耗
Context
尽管组件之间的通信是从上往下的流动方式,但是当传递数据是跨层级的,而且中间的子组件并不需要这些数据,使用props的传递方式就会大大增加人工成本,这个时候Context的出现就能解决这一问题(我觉得不是最简便的,数组只能在子组件的return下去使用,不能进行相应的数据处理,写法上也是比较复杂)
简而言之先导入createContext,然后将其函数执行结果解构赋值得到Provider,Consumer
const { Provider, Consumer: CounterConsumer } = createContext();
然后将数据源头或者发起者的return里将最外层包裹的div更改为Provider标签包裹,然后通过value属性将要传递的值传递下去
<Provider
value={{
count: this.state.count,
incrementCount: this.incrementCount,
decrementCount: this.decrementCount
}}
>
{this.props.children}
</Provider>
同样的将数据源头接受者的return里将最外层包裹的div更改为CounterConsumer (这里是重新赋值过了)标签包裹,然后写上箭头函数,它的第一个参数将接收到Provider提供的数据(就是Provider的state),通过语法糖的模式可以得到对应的数据从进行相关的数据处理
class Counter extends Component { //App组件下的组件
render() {
return (
<CounterConsumer>
{arg => {
return <span> {arg.count} </span>;
}}
</CounterConsumer>
);
}
}
import React, { Component, createContext } from "react";//首先导入createContext组件
import ReactDOM from "react-dom";
const { Provider, Consumer: CounterConsumer } = createContext();
//Provider包裹的组件是数据的源头,CounterConsumer是Consumer重新赋的值,被它包裹的组件可以使用
//Provider数据提供者的数据
class Counter extends Component { //App组件下的组件
render() {
return (
<CounterConsumer>
{arg => {
return <span> {arg.count} </span>;
}}
</CounterConsumer>
);
}
}
class CounteBtn extends Component { //App组件下的组件
render() {
return (
<CounterConsumer>
{Cal => {
const handler =
this.props.type === "increment"
? Cal.incrementCount
: Cal.decrementCount;
console.log(Cal);
return <button onClick={handler}>{this.props.children}</button>;
}}
</CounterConsumer>
);
}
}
class CounterProvider extends Component { //包裹App组件的组件,也就是数据的源头
constructor() {
super();
this.state = {
count: 100
};
}
incrementCount = () => {
this.setState({
count: this.state.count + 1
});
};
decrementCount = () => {
this.setState({
count: this.state.count - 1
});
};
render() {
return (
<Provider
value={{
count: this.state.count,
incrementCount: this.incrementCount,
decrementCount: this.decrementCount
}}
>
{this.props.children}
</Provider>
);
}
}
class App extends Component {
render() {
return (
<>
<CounteBtn type="decrement"> - </CounteBtn>
<Counter />
<CounteBtn type="increment"> + </CounteBtn>
</>
);
}
}
ReactDOM.render(
<CounterProvider>
<App />
</CounterProvider>,
document.getElementById("root")
);