注:!!!
由于内容较多,分2部分,前半部分:React基础篇(一)
二、React组件化开发
11.使用ES6装饰器语法简化高阶组件的使用
1.下载插件包: @babel/plugin-proposal-decorators
2.配置插件: config-overrides.js中: addDecoratorsLegacy()
const {addDecoratorsLegacy} = require('customize-cra');
module.exports = override(
...,
// 添加装饰器的配置
addDecoratorsLegacy()
);
3.使用:
1)装饰connect高阶组件: @connect(null, {search})
2)装饰withRouter高阶组件: @withRouter
3)装饰器语法的本质:
//装饰器语法,让MyClass多了一个a属性
@demo
class MyClass { }
function demo(target) {
target.a = 1;
}
等同于
//正常语法,让MyClass多了一个a属性
class MyClass { }
function demo(target) {
target.a = 1;
}
MyClass = demo(MyClass)
12.组件的生命周期 (旧)(了解!)
1-理解
1)组件从创建到死亡它会经历一些特定的阶段。
2)React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
3)我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2-组件的生命周期 (旧)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染
1) constructor(): 创建组件对象的初始化方法
2) componentWillMount(): 组件即将被装载、渲染到页面上
3) render(): 组件在这里生成虚拟的DOM节点
4) componentDidMount(): 组件真正在被装载之后 => 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息、只执行一次。
2.更新阶段: 由组件内部this.setSate()或父组件render触发
1) componentWillReceiveProps(): 组件将要接收到属性的时候调用
2) shouldComponentUpdate(): 组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用重绘)
3) componentWillUpdate(): 组件即将更新不能修改属性和状态
4) render(): 组件重新描绘 => 必须使用的一个
5) componentDidUpdate(): 组件已经更新
3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1) componentWillUnmount(): 组件即将销毁 => 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息、只执行一次。
图解:
实例:
需求: 定义一个求和的组件
点击按钮,上方显示的总和+1
<div id="test"></div>
<script type="text/babel">
//创建组件
class Count extends React.Component{
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state;
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//测试父组件render
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
//子组件B
class B extends React.Component{
//组件将要接收新的props的钩子
//此钩子有坑,第一次接收props不会调用
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
//渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
// ReactDOM.render(<A/>,document.getElementById('test'))
</script>
13.组件的生命周期 (新) - 16.4之后(重点!)
1-组件的生命周期 (新)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染
1) constructor()
2) getDerivedStateFromProps()
3) render()
4) componentDidMount() => 常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息、只执行一次。
3.更新阶段: 由组件内部this.setSate()或父组件重新render触发
1) getDerivedStateFromProps()
2) shouldComponentUpdate()
3) render()
4) getSnapshotBeforeUpdate()
5) componentDidUpdate()
4.卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1) componentWillUnmount() => 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息、只执行一次。
2-新旧生命周期的区别
1) 新生命周期废弃了3个钩子(componentWillMount,componentWillUpdate,componentWillReceiveProps),
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
UNSAFE_componentWillMount(){
console.log('---componentWillMount---')
}
UNSAFE_componentWillUpdate(){
console.log('---componentWillUpdate---')
}
UNSAFE_componentWillReceiveProps(){
console.log('---componentWillReceiveProps---')
}
2) 新提出了2个钩子 getDerivedStateFromProps 和 getSnapshotBeforeUpdate
图解:
实例:
需求: 定义一个求和的组件
点击按钮,上方显示的总和+1
<div id="test"></div>
<script type="text/babel">
//创建组件
class Count extends React.Component{
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//(了解)若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
//此方法不是实例调用的,必须写static
//此方法必须返回state对象(跟状态内容一模一样)或null
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null;
//返回由props得到的state的对象
// return props;
}
//在更新之前获取快照
//此方法必须返回snapshot value(快照值)或null
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true;
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Count count={199}/>,document.getElementById('test'));
</script>
扩展:getSnapShotBeforeUpdate的使用场景
-
getSnapshotBeforeUpdate有什么用?———— 在页面真正发生更新之前,保留下原来页面中DOM的信息
-
注:
1.此方法必须配合componentDidUpdate使用。
2.只有返回的是DOM相关的值,才有意义。
3.componentDidUpdate会收到三个参数:preProps,preState,snapshotValue
实例:
<div id="test"></div>
<script type="text/babel">
class NewsList extends React.Component{
state = {newsArr:[]};
componentDidMount(){
setInterval(() => {
//获取原状态
const {newsArr} = this.state;
//模拟一条新闻
const news = '新闻'+ (newsArr.length+1);
//更新状态
this.setState({newsArr:[news,...newsArr]});
}, 1000);
}
//获取快照在更新之前
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight;
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
// this.refs.list.scrollTop += 30;
}
render(){
return(
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList/>,document.getElementById('test'))
</script>
14.虚拟DOM、DOM diff算法与key的作用(重难点!)
1) 虚拟DOM深入
1.本质是Object类型的对象(一般对象), 准确的说是一个对象树(倒立的)。
2.虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
3.如果只是更新虚拟DOM, 页面是不会重绘的。
2). DOM diff算法的基本步骤(重点!)
1.用JS对象树表示DOM树的结构, 然后用这个树构建一个真正的DOM树插到文档当中。
2.当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。
3.把差异应用到真实DOM树上,视图就更新了。
3) 进一步理解(重点!)
-
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
-
可以类比 CPU 和 硬盘,既然硬盘这么慢,我们就在它们之间加个缓存, 既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。
-
CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
图解:
4) 验证diff算法的存在
<div id="test"></div>
<script type="text/babel">
class Time extends React.Component {
state = {date: new Date()}
componentDidMount () {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
render () {
return (
<div>
<h1>hello</h1> //直接复用,不更新
<input type="text"/> //直接复用,不更新
<span>
现在是:{this.state.date.toTimeString()}
//此处的input不会更新,直接复用,因为diff比较的最小粒度是标签
<input type="text"/>
</span>
</div>
)
}
}
ReactDOM.render(<Time/>,document.getElementById('test'))
</script>
5) React中key的作用(重点!)
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也是可以的。
扩展: diff中key的比较过程
1.使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
2.使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
实例
<div id="test"></div>
<script type="text/babel">
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add = ()=>{
const {persons} = this.state;
const p = {id:persons.length+1,name:'小王',age:20};
this.setState({persons:[p,...persons]});
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>,document.getElementById('test'));
</script>
15.使用React开发者工具调试
-
chrome应用商店安装React Developer Tools
-
浏览器调试工具在用react开发的网页中会出现Components选项,用于开发者进行组件调试
16.React性能优化
16.1组件优化 - 类组件
1.Component的2个问题
1)只要执行setState(), 即使不改变状态数据, 组件也会重新render() => 效率低
- 如:this.setState({})
2)只当前组件重新render(), 就会自动重新render子组件,即使子组件没有用到父组件的任何数据 => 效率低
2.原因
- Component中的 shouldComponentUpdate() 总是返回 true
3.效率高的做法
- 只有当组件的state或props数据发生改变时才重新render()
4.解决
1)方法1 :重写shouldComponentUpdate(nextProps, nextState)方法
- 比较新旧 state 或 props 数据, 如果有变化才返回 true, 如果没有返回 false
2)方法2 :将类继承PureComponent
-
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
-
但只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
-
所以不要直接修改state数据, 而是要产生新数据
注:
- 项目中一般使用PureComponent来优化
实例
import React, { PureComponent } from 'react'
import './index.css'
export default class Parent extends PureComponent {
state = {carName:"奔驰c36",stus:['小张','小李','小王']}
addStu = ()=>{
/* const {stus} = this.state
stus.unshift('小刘')
this.setState({stus}) */
const {stus} = this.state
this.setState({stus:['小刘',...stus]})
}
changeCar = ()=>{
//this.setState({carName:'迈巴赫'})
const obj = this.state
obj.carName = '迈巴赫'
console.log(obj === this.state);
this.setState(obj)
}
/* shouldComponentUpdate(nextProps,nextState){
// console.log(this.props,this.state); //目前的props和state
// console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.state.carName === nextState.carName
} */
render() {
console.log('Parent---render');
const {carName} = this.state
return (
<div className="parent">
<h3>我是Parent组件</h3>
{this.state.stus}
<span>我的车名字是:{carName}</span><br/>
<button onClick={this.changeCar}>点我换车</button>
<button onClick={this.addStu}>添加一个小刘</button>
<Child carName="奥拓"/>
</div>
)
}
}
class Child extends PureComponent {
/* shouldComponentUpdate(nextProps,nextState){
console.log(this.props,this.state); //目前的props和state
console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.props.carName === nextProps.carName
} */
render() {
console.log('Child---render');
return (
<div className="child">
<h3>我是Child组件</h3>
<span>我接到的车是:{this.props.carName}</span>
</div>
)
}
}
16.2高阶组件memo - 函数式组件优化
1.memo()包裹函数组件,返回一个优化后的新组件
import React, { PureComponent, memo } from 'react';
// Header
const MemoHeader = memo(function Header() {
console.log("Header被调用");
return <h2>我是Header组件</h2>
})
class Banner extends PureComponent {
render() {
console.log("Banner render函数被调用");
return <h3>我是Banner组件</h3>
}
}
const MemoProductList = memo(function ProductList() {
console.log("ProductList被调用");
return (
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
)
})
// Main
class Main extends PureComponent {
render() {
console.log("Main render函数被调用");
return (
<div>
<Banner/>
<MemoProductList/>
</div>
)
}
}
// Footer
const MemoFooter = memo(function Footer() {
console.log("Footer被调用");
return <h2>我是Footer组件</h2>
})
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
<MemoHeader/>
<Main/>
<MemoFooter/>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
17.Fragment
1.使用
<Fragment><Fragment> 只能设置key属性
或
<></> 不能传任何属性
2.作用
- 可以不用必须有一个真实的DOM根标签了
3.特点
- 此组件不会加载到dom中
实例
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
)
}
}
18.Portals的使用
- 某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元素上的)。
- Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- ReactDOM.createPortal(child, container);
- 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;
- 第二个参数(container)是一个 DOM 元素;
- 通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:
- 然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的。
实例:
1.默认情况
render() {
return (
//React挂载了一个新的div,并且把子元素渲染其中
<div>
{this.props.children}
</div>
);
}
2.Portals的使用
render() {
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
Modal组件案例
- 开发一个Modal组件,它可以将它的子组件渲染到屏幕的中间位置:
- 步骤一:修改index.html添加新的节点
- 步骤二:编写这个节点的样式
- 步骤三:编写组件代码
//index.css
#modal {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
//index.html
<div id="root"></div>
<div id="modal"></div>
//app.js
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import "./css/index.css";
class Modal extends PureComponent {
render() {
return ReactDOM.createPortal(
this.props.children,
document.getElementById("modal")
)
}
}
class Home extends PureComponent {
render() {
return (
<div>
<h2>Home</h2>
<Modal>
<h2>Title</h2>
</Modal>
</div>
)
}
}
export default class App extends PureComponent {
render() {
return (
<div>
<Home/>
</div>
)
}
}
19.StrictMode
- StrictMode 是一个用来突出显示应用程序中潜在问题的工具。
- 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;
- 它为其后代元素触发额外的检查和警告;
- 严格模式检查仅在开发模式下运行;它们不会影响生产构建;
- 可以为应用程序的任何部分启用严格模式:
- 不会对 Header 和 Footer 组件运行严格模式检查;
- 但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查;
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
-
严格模式检查的是什么?
-
1.识别不安全的生命周期
-
2.使用过时的ref API
-
3.使用废弃的findDOMNode方法
- 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了
-
4.检查意外的副作用
-
这个组件的constructor会被调用两次;
-
这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
-
在生产环境中,是不会被调用两次的;
-
-
5.检测过时的context API
-
早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的;
-
目前这种方式已经不推荐使用
-
-
三、React中的样式
1.组件化天下的CSS
-
整个前端已经是组件化的天下:
- 而CSS的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案。
-
在组件化中选择合适的CSS解决方案应该符合以下条件:
- 可以编写局部css:css具备自己的具备作用域,不会随意污染其他组件内的原生;
- 可以编写动态的css:可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;
- 支持所有的css特性:伪类、动画、媒体查询等;
- 编写起来简洁方便、最好符合一贯的css风格特点;
- ...
2.React中的CSS
-
事实上,css一直是React的痛点,也是被很多开发者吐槽、诟病的一个点。
-
在这一点上,Vue做的要确实要好于React:
-
Vue通过在.vue文件中编写 < style >< /style > 标签来编写自己的样式;
-
通过是否添加 scoped 属性来决定编写的样式是全局有效还是局部有效;
-
通过 lang 属性来设置你喜欢的 less、sass等预处理器;
-
通过内联样式风格的方式来根据最新状态设置和改变css;
-
...
-
-
Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目采用不一样的样式风格。
-
相比而言,React官方并没有给出在React中统一的样式风格:
-
由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
-
大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;
-
3.内联样式
-
内联样式是官方推荐的一种css样式的写法:
-
style 接受一个采用小驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串;
-
并且可以引用state中的状态来设置相关的样式;
-
-
内联样式的优点:
-
1.内联样式, 样式之间不会有冲突
-
2.可以动态获取当前state中的状态
-
-
内联样式的缺点:
-
1.写法上都需要使用驼峰标识
-
2.某些样式没有提示
-
3.大量的样式, 代码混乱
-
4.某些样式无法编写(比如伪类/伪元素)
-
-
所以官方依然是希望内联合适和普通的css来结合编写;
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
color: "purple"
}
}
render() {
const pStyle = {
color: this.state.color,
textDecoration: "underline"
}
return (
<div>
<h2 style={{fontSize: "50px", color: "red"}}>我是标题</h2>
<p style={pStyle}>我是一段文字描述</p>
</div>
)
}
}
4.普通的css
-
普通的css我们通常会编写到一个单独的文件,之后再进行引入。
-
这样的编写方式和普通的网页开发中编写方式是一致的:
-
如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
-
但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
-
但是普通的css都属于全局的css,样式之间会相互影响;
-
-
这种编写方式最大的问题是样式之间会相互层叠掉;
// Profile/style.css
.profile .title {
color: yellow;
}
// Profile/index.js
import React, { PureComponent } from 'react';
import './style.css';
export default class Profile extends PureComponent {
render() {
return (
<div className="profile">
<h2 className="title">我是Profile的标题</h2>
<ul className="settings">
<li>设置列表1</li>
<li>设置列表2</li>
<li>设置列表3</li>
</ul>
</div>
)
}
}
// Home/style.css
.home .title {
font-size: 30px;
color: red;
}
.home .banner {
color: orange;
}
// Home/index.js
import React, { PureComponent } from 'react';
import './style.css';
export default class Home extends PureComponent {
render() {
return (
<div className="home">
<h2 className="title">我是home的标题</h2>
<div className="banner">
<span>轮播图</span>
</div>
</div>
)
}
}
// App/style.css
#app > .title {
color: blue;
}
// App/index.js
import React, { PureComponent } from 'react';
import './style.css';
import Home from '../home';
import Profile from '../profile';
export default class App extends PureComponent {
render() {
return (
<div id="app">
App
<h2 className="title">我是App的title</h2>
<Home/>
<Profile/>
</div>
)
}
}
5.css modules
-
css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
- 但是,如果在其他项目中使用,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
-
React的脚手架已经内置了css modules的配置:
-
.css/.less/.scss 等样式文件都修改成 .module.css/.module.less/.module.scss 等;
-
之后就可以引用并且进行使用了;
-
-
css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。
-
但是这种方案也有自己的缺陷:
-
引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
-
所有的className都必须使用{style.className} 的形式来编写;
-
不方便动态来修改某些样式,依然需要使用内联样式的方式;
-
-
如果你觉得上面的缺陷还算OK,那么你在开发中完全可以选择使用css modules来编写,并且也是在React中很受欢迎的一种方式。
// Profile/style.module.css
.title {
color: yellow;
}
.setting {
}
.settingItem {
}
// Profile/index.js
import React, { PureComponent } from 'react';
import style from './style.module.css';
export default class Profile extends PureComponent {
constructor(props) {
super(props);
this.state = {
color: "purple"
}
}
render() {
return (
<div className="profile">
<h2 className={style.title} style={{color: this.state.color}}>我是Profile的标题</h2>
<ul className={style.settings}>
<li className={style.settingItem}>设置列表1</li>
<li>设置列表2</li>
<li>设置列表3</li>
</ul>
</div>
)
}
}
// Home/.module.css
.title {
font-size: 30px;
color: red;
}
.banner {
color: orange;
}
// Home/index.js
import React, { PureComponent } from 'react';
import homeStyle from './style.module.css';
export default class Home extends PureComponent {
render() {
return (
<div className="home">
<h2 className={homeStyle.title}>我是home的标题</h2>
<div className={homeStyle.banner}>
<span>轮播图</span>
</div>
</div>
)
}
}
// App/.module.css
.title {
color: blue;
}
// App/index.js
import React, { PureComponent } from 'react';
import appStyle from './style.module.css';
import Home from '../home';
import Profile from '../profile';
export default class App extends PureComponent {
render() {
return (
<div id="app">
App
<h2 className={appStyle.title}>我是App的title</h2>
<Home/>
<Profile/>
</div>
)
}
}
6.认识CSS in JS(推荐!)
-
实际上,官方文档也有提到过CSS in JS这种方案:
-
“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义;
-
注意此功能并不是 React 的一部分,而是由第三方库提供。 React 对样式如何定义并没有明确态度;
-
-
在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
-
但是在前面的学习中,我们就提到过,React的思想中认为逻辑本身和UI是无法分离的,所以才会有了JSX的语法。
-
样式呢?样式也是属于UI的一部分;
-
事实上CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态;
-
所以React有被人称之为 All in JS;
-
-
当然,这种开发的方式也受到了很多的批评:
-
Stop using CSS in JavaScript for web development
-
7.认识styled-components(推荐!)
-
批评声音虽然有,但是在我们看来很多优秀的CSS-in-JS的库依然非常强大、方便:
-
CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等等;
-
依然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;
-
所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案;
-
-
目前比较流行的CSS-in-JS的库有哪些呢?
-
styled-components
-
emotion
-
glamorous
-
-
目前可以说styled-components依然是社区最流行的CSS-in-JS库,所以我们以styled-components的讲解为主;
-
安装styled-components:
- yarn add styled-components
8.styled-components原理 - ES6标签模板字符串
-
ES6中增加了模板字符串的语法,这个对于很多人来说都会使用。
-
但是模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。
-
一个普通的JavaScript的函数:
-
正常情况下,我们都是通过 函数名() 方式来进行调用的,其实函数还有另外一种调用方式:
// 1.模板字符串的基本使用
const name = "why";
const age = 18;
const message = `my name is ${name}`;
// 2.标签模板字符串: 可以通过模板字符串的方式对一个函数进行调用
function test(...args) {
console.log(args);
}
// test("aaa", "ccc");
// test`aaaa`;
test`my name is ${name}, age is ${age}`;
test`
font-size: 15px;
color: red;
`
-
如果我们在调用的时候插入其他的变量:
-
模板字符串被拆分了;
-
第一个元素是数组,是被模块字符串拆分的字符串组合;
-
后面的元素是一个个模块字符串传入的内容;
-
-
在styled component中,就是通过这种方式来解析模块字符串,最终生成我们想要的样式的
9.styled的基本使用
-
styled-components的本质是通过函数的调用,最终创建出一个组件:
-
这个组件会被自动添加上一个不重复的class;
-
styled-components会给该class添加相关的样式;
-
-
另外,它支持类似于CSS预处理器一样的样 式嵌套:
-
支持直接子代选择器或后代选择器,并且直接编写样式;
-
可以通过&符号获取当前元素;
-
直接伪类选择器、伪元素等;
-
10.props、attrs属性
1.props可以穿透
<HYInput type="password" color={this.state.color}/>
2.props可以被传递给styled组件
-
获取props需要通过${}传入一个插值函数,props会作为该函数的参数;
-
这种方式可以有效的解决动态样式的问题;
3.添加attrs属性
const HYInput = styled.input.attrs({
placeholder: "coderwhy",
bColor: "red"
})`
background-color: lightblue;
border-color: ${props => props.bColor};
color: ${props => props.color};
`
11.styled高级特性
1.支持样式的继承
2.styled设置主题
12.styled-components的综合案例
// Profile/index.js
import React, { PureComponent } from 'react';
import styled from 'styled-components';
/**
* 特点:
* 1.props穿透
* 2.attrs的使用
* 3.传入state作为props属性
*/
//2.attrs的使用
const HYInput = styled.input.attrs({
placeholder: "coderwhy",
bColor: "red"
})`
background-color: lightblue;
border-color: ${props => props.bColor};
color: ${props => props.color};
`
export default class Profile extends PureComponent {
constructor(props) {
super(props);
this.state = {
color: "purple"
}
}
render() {
return (
<div>
<input type="password"/>
{/*
1.props穿透
3.传入state作为props属性
*/}
<HYInput type="password" color={this.state.color}/>
<h2>我是Profile的标题</h2>
<ul>
<li>设置列表1</li>
<li>设置列表2</li>
<li>设置列表3</li>
</ul>
</div>
)
}
}
// Home/style.js
import styled from 'styled-components';
export const HomeWrapper = styled.div`
font-size: 12px;
color: red;
.banner {
background-color: blue;
span {
color: #fff;
&.active {
color: red;
}
&:hover {
color: green;
}
&::after {
content: "aaa"
}
}
/* .active {
color: #f00;
} */
}
`
export const TitleWrapper = styled.h2`
text-decoration: underline;
color: ${props => props.theme.themeColor};
font-size: ${props => props.theme.fontSize};
`
// Home/index.js
import React, { PureComponent } from 'react';
import {
HomeWrapper,
TitleWrapper
} from "./style";
export default class Home extends PureComponent {
render() {
return (
<HomeWrapper>
<TitleWrapper>我是home的标题</TitleWrapper>
<div className="banner">
<span>轮播图1</span>
<span className="active">轮播图2</span>
<span>轮播图3</span>
<span>轮播图4</span>
</div>
</HomeWrapper>
)
}
}
// App/index.js
import React, { PureComponent } from 'react';
import Home from '../home';
import Profile from '../profile';
import styled, { ThemeProvider } from 'styled-components';
const HYButton = styled.button`
padding: 10px 20px;
border-color: red;
color: red;
`
// const HYPrimaryButton = styled.button`
// padding: 10px 20px;
// border-color: red;
// color: #fff;
// background-color: green;
// `
//样式继承
const HYPrimaryButton = styled(HYButton)`
color: #fff;
background-color: green;
`
export default class App extends PureComponent {
render() {
return (
//styled设置主题
<ThemeProvider theme={{themeColor: "red", fontSize: "30px"}}>
<Home />
<hr />
<Profile />
<HYButton>我是普通的按钮</HYButton>
<HYPrimaryButton>我是主要的按钮</HYPrimaryButton>
</ThemeProvider>
)
}
}
13.React中添加class
1.React在JSX给了我们开发者足够多的灵活性,你可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class:
- 原生React中添加class方法
{/* 原生React中添加class方法 */}
<h2 className={"foo bar active title"}>我是标题1</h2>
<h2 className={"title" + (isActive ? " active": "")}>我是标题2</h2>
<h2 className={["title", (isActive ? "active": "")].join(" ")}>我是标题3</h2>
2.使用第三方的库:classnames ,来简化写法
- 类似于vue中添加class方法
const {isActive} = this.state;
const isBar = false;
const errClass = "error";
const warnClass = 10;
{/* classnames库添加class */}
<h2 className="foo bar active title">我是标题4</h2>
<h2 className={classNames("foo", "bar", "active", "title")}>我是标题5</h2>
<h2 className={classNames({"active": isActive, "bar": isBar}, "title")}>我是标题6</h2>
<h2 className={classNames("foo", errClass, warnClass, {"active": isActive})}>我是标题7</h2>
<h2 className={classNames(["active", "title"])}>我是标题8</h2>
<h2 className={classNames(["active", "title", {"bar": isBar}])}>我是标题9</h2>