webpack从零构建react项目小二阅读器

项目地址:git地址小二阅读器
项目主要基于一个开源阅读器哦豁阅读器,为了更加深刻地理解react,我重构了一个纯react版的,没有使用redux,在这里跟大家分享一下过程。
首先看一下效果:

下面是开发过程中用到的npm库,这里列一下,后面使用时会单独指出来:

项目依赖库:

  1. antd 蚂蚁金服的一个react界面库,阅读器的界面主要基于此ui库
  2. react-router-dom react路由库
  3. store js本地存储库,主要用于书籍列表的本地存储,另外由于没有使用redux,各个组件的state的一致性也基于store库
  4. whatwg-fetch 异步请求fetch库,主要用于获取书籍信息
  5. react-tappable react点击组件库,用于获取用户点击事件

项目依赖开发库:

webpack、webpack-dev-server、babel-core、babel-loader、babel-preset-env、babel-preset-react、babel-plugin-import、css-loader、less-loader、style-loader、url-loader

主要用到的库就这些,下面进入正题。

一、 webpack配置项目

由于项目没有使用脚手架create_react_app,需要自己从头开始配置,这样也是为了更好地理解开发过程。

  • cd '项目目录'
  • npm init 生成package.json文件
  • npm install --save react react-dom antd react-router-dom store whatwg-fetch react-tappable
  • npm install --save-dev webpack webpack-dev-server babel-core babel-loader babel-preset-env babel-preset-react babel-plugin-import css-loader style-loader less-loader url-loader

上述完成后在项目目录里创建webpack.config.js和.babelrc文件,其中webpack.config.js内容如下:

const webpack = require('webpack');

module.exports = {
    entry:  __dirname + "/src/main.js",//已多次提及的唯一入口文件
    devtool: 'eval-source-map',output: {
        path: __dirname + "/public",//打包后的文件存放的地方
        filename: "bundle.js"//打包后输出文件的文件名
    },plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ],devServer: {
        contentBase: './public',historyApiFallback: true,inline: true,//代理设置,本地开发时需要设置代理,不然无法获取数据
        proxy: {
            '/api': {
                target: 'http://api.zhuishushenqi.com/',pathRewrite: {'^/api' : '/'},changeOrigin: true
            },'/chapter': {
                target: 'http://chapter2.zhuishushenqi.com/',pathRewrite: {'^/chapter' : '/chapter'},changeOrigin: true
            }
        }
    },module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,use: { loader: 'babel-loader'},exclude: /node_modules/
            },{
                test: /\.css$/,use: [{loader:'style-loader'},{loader:'css-loader?modules&localIdentName=[name]_[local]-[hash:base64:5]'}]
            },{
                test: /\.(png|jpg|gif|woff|woff2)$/,loader: 'url-loader?limit=8192'
            },{
                test: /\.less/,loader: 'style-loader!css-loader!less-loader'
            }
        ]
    }
}

.babelrc文件内容如下:

{
  "presets": ["react","env"],"plugins": [["import",{ "libraryName": "antd","style": true }]]
}

另外在package.json文件中设置

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1","start": "webpack","server": "webpack-dev-server --open"
}

这样在终端中运行npm run server会有一个本地测试环境
以上就是项目配置了,下面进入正式的开发过程。

二、内容开发

这里只简要讲一下流程,具体内容可查看项目源码
1.阅读器首页组件home.js
阅读器首页头部有一个搜索图标、一个下拉图标,点击搜索图标进入搜索组件,下拉图标有‘我的’和‘关于’路由,‘我的’组件目前没有做,‘关于组件’就是一个简单的文字显示组件。首页的内容是之前通过搜索加入书架的书籍,这里需要一个列表显示,数据呢从本地存储里面获取,这里用到了store.js库,搜索书籍的时候将加入书架的书籍存储到本地localStorage中,首页就可以获取相关数据了。

import React,{ Component } from 'react';
import {Layout,Menu,Dropdown,Icon} from 'antd';//具体使用方法可查看antd官方文档
import styles from './home.css';
import store from 'store/dist/store.legacy';
import { Link } from 'react-router-dom';
import BookItem from './bookItem';

const {Header,Content} = Layout;
let menuPng = require('./images/menu.png');//加载图片地址这里用到了url-loader

class App extends Component {
    constructor(props){
        super(props);
        //下拉框下拉内容
        this.menu = (
            <Menu>
                <Menu.Item key='0'>
                    <a href="#"><Icon type="user" /> 我的</a>
                </Menu.Item>
                <Menu.Item>
                    <Link to="/about"><Icon type="copyright" /> 关于</Link>
                </Menu.Item>
            </Menu>
        );
        this.state = {
            //从本地存储中获取书籍列表,是一个数组,数组里面存放的是书籍信息
            bookList: store.get('bookList')||[]
        };
        //长按书籍列表删除书籍
        this.deleteBook = (key)=>{
            let bookList = store.get('bookList');
            let bookIdList = store.get('bookIdList');
            bookList.splice(key,1);
            bookIdList.splice(key,1);
            store.set('bookList',bookList);
            store.set('bookIdList',bookIdList);
            this.setState({bookList:bookList});
        }
    }
    componentDidMount(){
    }
    render() {
        return (
                <Layout>
                    <Header className={styles.header}>
                        <span className={styles.title}>小二阅读</span>
                        <Dropdown overlay={this.menu} placement="bottomRight">
                            <img src={menuPng} className={styles.dropdown}/>
                        </Dropdown>
                        <Link to='/search'><Icon type="search" className={styles.search}/></Link>
                    </Header>
                    <Content className={styles.content}>
                        {
                            this.state.bookList.length===0?(
                                <div className={styles.null}>书架空空如也,快去添加吧!</div>
                            ):this.state.bookList.map((item,index)=>(
                                <Link to={`/read/${index}`} key={index}><BookItem data={item} deleteBook={this.deleteBook} key={index} arg={index}/></Link>
                            ))
                        }
                    </Content>
                </Layout>
        );
    }
}

export default App;

2.阅读器搜索组件search.js
搜索组件头部有一个返回图标,一个输入框和一个搜索图标,用户通过输入书名点击搜索图标或按enter键进行搜索,组件里面有一个函数获取用户输入发起异步请求获取书籍信息,这里主要是一些antd组件的使用,方法都可以查询官方文档

import React from 'react';
import {Layout,Icon,Input,Spin,Tag} from 'antd';
import { Link } from 'react-router-dom';
import styles from './search.css';
import store from 'store/dist/store.legacy';
import randomcolor from 'randomcolor';
import 'whatwg-fetch';
import ResultBookItem from './resultBookItem';
import {url2Real} from "./method";

const { Header,Content } = Layout;


class Search extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            searchValue: '',bookList: [],loading: false,searchHistory: store.get('searchHistory') || []
        };
        this.flag = this.state.searchValue.length ? false : true;
        this.tagColorArr = this.state.searchHistory.map(item => randomcolor({luminosity: 'dark'}));
        this.clearHistory = ()=>{
            let searchHistory = [];
            this.setState({searchHistory});
            store.set('searchHistory',searchHistory);
        };
        this.searchBook = (value)=>{
            this.flag = false;
            value = value === undefined ? this.state.searchValue : value;
            if (new Set(value).has(' ') || value === '') {
                alert('输入为空!');
                return;
            };
            //更新搜索历史
            let searchHistory = new Set(this.state.searchHistory);
            if(!searchHistory.has(value)){
                searchHistory = this.state.searchHistory;
                searchHistory.unshift(value);
                store.set('searchHistory',searchHistory);
            }
            this.tagColorArr.push(randomcolor({luminosity: 'dark'}));
            this.setState({loading:true,searchHistory});
            //发起异步请求获取书籍信息
            fetch(`/api/book/fuzzy-search?query=${value}&start=0`)
                .then(res=>res.json())
                .then(data => {
                    data.books.map((item)=>{item.cover=url2Real(item.cover);});
                    return data.books;
                })
                .then(data=>{
                    this.setState({bookList:data,loading:false});
                })
                .catch(err=>{console.log(err)});
        }
        this.handleChange = (e)=>{
            this.setState({
                searchValue:e.target.value
            });
        }
        this.wordSearch = (e)=>{
            let word = e.target.textContent;
            this.setState({searchValue: word});
            this.searchBook(word);
        }
        this.clearInput = () => {
            this.flag = true;
            this.setState({searchValue:''});
        }
    }

    render(){
        return (
            <Layout>
                <Header className={styles.header}>
                    <Link to="/"><Icon type="arrow-left" className={styles.pre}/></Link>
                    <Input
                        ref="search"
                        placeholder="请输入搜索的书名"
                        className={styles.searchInput}
                        value={this.state.searchValue}
                        onChange={this.handleChange}
                        onPressEnter={ () => this.searchBook()}
                        suffix={<Icon type="close-circle" onClick={this.clearInput} />}
                    />
                    <Icon type='search' className={styles.search} onClick={() => this.searchBook()}/>
                </Header>
                <Spin className={styles.loading} spinning={this.state.loading} tip="书籍搜索中...">
                    <Content className={styles.content}>
                        {
                            this.flag ? (
                                    <div className='tagBox'>
                                        <h2>最近搜索历史</h2>
                                        <div className={styles.tags}>
                                            {
                                                this.state.searchHistory.map((item,index) =>
                                                    <Tag onClick={this.wordSearch} className={styles.tag} color={this.tagColorArr[index]} key={index}>{item}</Tag>
                                                )
                                            }
                                        </div>
                                        <div className={styles.clear} onClick={this.clearHistory}><Icon type="delete" />清空搜索历史</div>
                                    </div>
                                )
                                :
                                (
                                    this.state.bookList.length !== 0 ?
                                        this.state.bookList.map((item,index) => <ResultBookItem data={item} key={index}/>)
                                        : (<div className={styles.noResult}>没有找到搜索结果</div>)
                                )
                        }
                    </Content>
                </Spin>
            </Layout>
        )
    }
}

export default Search;

3.阅读器书籍详情组件bookIntroduce.js
这个组件是用户点击搜索出来的书籍进入的组件,会显示相关书籍的一个较详细信息,用户点击搜索出来的书籍会向组件传入一个id,组件根据id在componentDidMount方法里发起异步请求获取书籍详细信息然后显示,用户点击追更新按钮时会调用addBook函数继续发起异步请求获取更详细的书籍信息并将信息保存在本地存储中,用户点击阅读按钮时同样会由addBook发起异步请求,不过这一次会进入阅读界面,同时将书籍保存在本地存储中。

import React from 'react';
import {Layout,Button,Tag,message,Modal} from 'antd';
import { Link } from 'react-router-dom';
import styles from './bookIntroduce.css';
import randomcolor from 'randomcolor';
import {
    time2Str,url2Real,wordCount2Str
} from './method.js';
import store from 'store/dist/store.legacy';

const {Header,Content} = Layout;

let errorLoading = require('./images/error.jpg');

class BookIntroduce extends React.Component{
    constructor(props){
        super(props);
        this.state={
            loading:true,save:false,data:{}
        };
        message.config({top:500,duration:2});

        this.addBook = ()=>{
            let dataIntroduce = this.state.data;
            fetch(`/api/toc?view=summary&book=${this.state.data._id}`)
                .then(res=>res.json())
                .then(data=>{
                    let sourceId = data.length>1?data[1]._id:data[0]._id;
                    for(let item of data){
                        if(item.source === 'my176'){
                            sourceId = item._id;
                        }
                    }
                    dataIntroduce.sourceId = sourceId;
                    return fetch(`/api/toc/${sourceId}?view=chapters`);
                })
                .then(res=>res.json())
                .then(data=>{
                    data.readIndex = 0;
                    dataIntroduce.list = data;
                    let localList = store.get('bookList')||[];
                    let localIdList = store.get('bookIdList')||[];
                    if(localIdList.indexOf(dataIntroduce._id)!==-1){
                        message.info('书籍已在书架中');return;
                    }
                    localList.unshift(dataIntroduce);
                    localIdList.unshift(dataIntroduce._id);
                    store.set('bookList',localList);
                    store.set('bookIdList',localIdList);
                    message.info(`《${this.state.data.title}》加入书架`);
                    this.setState({save:true});
                    return;
                })
                .catch(err=>{console.log(err)});
        }
        this.readBook = ()=>{
            this.addBook();
            //react-router-dom 页面跳转
            this.props.history.push({pathname: '/read/' + 0});
        }
        this.deleteBook = ()=>{
            let localList = store.get('bookList');
            let localIdList = store.get('bookIdList');
            localList.shift();
            localIdList.shift();
            store.set('bookList',localList);
            store.set('bookIdList',localIdList);
            this.setState({save:false});
        }
    }
    componentDidMount(){
        fetch(`/api/book/${this.props.match.params.id}`)
            .then(res=>res.json())
            .then(data=>{
                data.cover = url2Real(data.cover);
                data.wordCount = wordCount2Str(data.wordCount);
                data.updated = time2Str(data.updated);
                this.setState({data:data,loading:false});
            })
            .catch(err=>console.log(err));
    }
    handleImageErrored(e){
        e.target.src = errorLoading;
    }

    render(){
        return (
            <Layout>
                <Header className={styles.header}>
                    <Link to={'/search'}><Icon type="arrow-left" className={styles.pre} /></Link>
                    <span className={styles.title}>书籍详情</span>
                </Header>
                <Spin className={styles.loading} spinning={this.state.loading} tip="书籍详情正在加载中...">
                    <Content className={styles.content}>
                        {
                        this.state.loading?'':(
                            <div>
                                <div className={styles.box}>
                                    <img src={this.state.data.cover} onError={this.handleImageErrored}/>
                                    <p>
                                        <span className={styles.bookName}>{this.state.data.title}</span><br/>
                                        <span className={styles.bookMsg}><em>{this.state.data.author}</em> | {this.state.data.minorCate} | {this.state.data.wordCount}</span>
                                        <span className={styles.updated}>{this.state.data.updated}前更新</span>
                                    </p>
                                </div>
                                <div className={styles.control}>
                                    {
                                        this.state.save ?
                                            (<Button icon='minus' size='large' className={styles.cancel} onClick={this.deleteBook}>移出书架</Button>) :
                                            (<Button icon='plus' size='large' onClick={this.addBook}>加入书架</Button>)
                                    }
                                    <Button icon='search' size='large' onClick={this.readBook}>开始阅读</Button>
                                </div>
                                <div className={styles.number}>
                                    <p><span>追书人数</span><br/>{this.state.data.latelyFollower}</p>
                                    <p><span>读者留存率</span><br/>{this.state.data.retentionRatio}%</p>
                                    <p><span>日更新字数</span><br/>{this.state.data.serializeWordCount}</p>
                                </div>
                                <div className={styles.tags}>
                                    {
                                        this.state.data.tags.map((item,index) =>
                                            <Tag className={styles.tag} color={randomcolor({luminosity: 'dark'})} key={index}>{item}</Tag>
                                        )
                                    }
                                </div>
                                <div className={styles.introduce}>
                                    <p>{this.state.data.longIntro}</p>
                                </div>
                            </div>
                        )
                        }
                    </Content>
                </Spin>
            </Layout>
        );
    }
}

export default BookIntroduce;

4.阅读器阅读组件read.js
阅读组件主要获取书籍章节信息并显示,该组件实现了章节选取,字体调整,背景调整等功能,具体可查看源码实现

import React from 'react';
import { Link } from 'react-router-dom'
import {Layout,Modal} from 'antd';
import styles from './read.less';
import 'whatwg-fetch';
import store from 'store/dist/store.legacy';

const { Header,Footer } = Layout;
var _ = require('underscore');

class Read extends React.Component{
    constructor(props) {
        super(props);
        this.flag = true; //标记第一次进入, 判断是否读取上一次阅读的scrollTop
        this.pos = this.props.match.params.id; //书籍在列表的序号
        this.index = store.get('bookList')[this.pos].readIndex || 0; //章节号
        this.chapterList = store.get('bookList')[this.pos].list.chapters;
        //this.readSetting = store.get('readSetting') || {fontSize: '18',backgroundColor: 'rgb(196,196,196)'};
        this.state = {
            loading: true,chapter: '',show: false,readSetting: store.get('readSetting') || {fontSize: '18',196)'},chapterListShow: false,readSettingShow: false
        }
        this.getChapter = (index) => {
            if (index < 0) {
                message.info('已经是第一章了!');
                this.index = 0;
                return;
            }
            else if(index >= this.chapterList.length) {
                message.info('已经是最新的一章了!');
                this.index = this.chapterList.length - 1;
                index = this.index;
            }
            this.setState({loading: true});
            let chapters = store.get('bookList')[this.pos].list.chapters;
            if (_.has(chapters[index],'chapter')) {
                this.setState({loading: false,chapter: chapters[index].chapter},() => {
                    this.refs.box.scrollTop = 0;
                });
                let bookList = store.get('bookList');
                bookList[this.pos].readIndex = index;
                store.set('bookList',bookList);
                return;
            }
            fetch(`/chapter/${encodeURIComponent(this.chapterList[index].link)}?k=2124b73d7e2e1945&t=1468223717`)
                .then(res => res.json())
                .then( data => {
                    if (!data.ok) {
                        message.info('章节内容丢失!');
                        return this.setState({loading: false});
                    }
                    let content = _.has(data.chapter,'cpContent') ?  data.chapter.cpContent :  data.chapter.body;
                    data.chapter.cpContent =  '   ' + content.replace(/\n/g,"\n   ");
                    data.chapter.title = this.chapterList[index].title;

                    let bookList = store.get('bookList');
                    bookList[this.pos].readIndex = index;
                    store.set('bookList',bookList);

                    this.setState({loading: false,chapter: data.chapter})
                })
                .catch(error => message.info(error))
        }

        this.nextChapter = (e) => {
            e.stopPropagation();
            this.getChapter(++this.index);
        }
        this.preChapter = (e) => {
            e.stopPropagation();
            this.getChapter(--this.index);
        }

        this.targetChapter = (e) => {
            e.stopPropagation();
            this.index = e.target.id
            this.getChapter(this.index);
            this.setState({chapterListShow: false});
        }

        this.showSetting = () => {
            this.setState({show: !this.state.show});
        }

        this.fontUp = () => {
            let setting = {};
            Object.assign(setting,this.state.readSetting);
            setting.fontSize++;
            //this.readSetting.fontSize++;
            this.setState({readSetting: setting});
            store.set('readSetting',this.readSetting);
        }
        this.fontDown = () => {
            if (this.state.readSetting.fontSize <=12) {
                return;
            }
            let setting = {};
            Object.assign(setting,this.state.readSetting);
            setting.fontSize--;
            this.setState({readSetting: setting});
            store.set('readSetting',this.readSetting);
        }

        this.changeBackgroudColor = (e) => {
            let setting = {};
            Object.assign(setting,this.state.readSetting);
            setting.backgroundColor = e.target.style.backgroundColor;
            this.setState({readSetting: setting});
            store.set('readSetting',this.readSetting);
        }

        this.readScroll = () => {
            let bookList = store.get('bookList');
            bookList[this.pos].readScroll = this.refs.box.scrollTop;
            store.set('bookList',bookList);
        }

        this.showChapterList = (chapterListShow) => {
            this.setState({ chapterListShow });
        }

        this.downloadBook = () => {
            let pos = this.pos;
            Modal.confirm({
                title: '缓存',content: (
                    <div>
                        <p>是否缓存后100章节?</p>
                    </div>
                ),onOk() {
                    let bookList = store.get('bookList');
                    let chapters = bookList[pos].list.chapters;
                    let download = (start,end) => {
                        if (start > end || start >= chapters.length) {
                            message.info('缓存完成');
                            return;
                        }
                        if(_.has(chapters[start],'chapter')) {
                            download(++start,end);
                            return;
                        }
                        fetch(`/chapter/${encodeURIComponent(chapters[start].link)}?k=2124b73d7e2e1945&t=1468223717`)
                            .then(res => res.json())
                            .then( data => {
                                let content = _.has(data.chapter,'cpContent') ?  data.chapter.cpContent :  data.chapter.body;
                                data.chapter.cpContent =  '   ' + content.replace(/\n/g,"\n   ");
                                chapters[start].chapter = data.chapter;
                                bookList[pos].list.chapters = chapters;
                                store.set('bookList',bookList);
                                download(++start,end);
                            })
                            .catch(error => message.info(error))
                    }

                    for(let i = 0; i < bookList[pos].readIndex; i++) {
                        delete chapters[i].chapter;
                    }

                    download(bookList[pos].readIndex,bookList[pos].readIndex + 100);
                },onCancel() {
                },});
        }

        this.readSettingShowControl = (e) => {
            e.stopPropagation();
            let value = !this.state.readSettingShow;
            this.setState({readSettingShow: value});
        }
    }


    componentWillMount() {
        this.getChapter(this.index);

        // 刷新最近阅读的书籍列表顺序
        let bookList = store.get('bookList');
        bookList.unshift(bookList.splice(this.pos,1)[0]);
        store.set('bookList',bookList);
        this.pos = 0;
    }


    componentDidUpdate(prevProps,prevState) {
        if (this.flag) { //加载上次阅读进度
            let bookList = store.get('bookList');
            this.refs.box.scrollTop = _.has(bookList[this.pos],'readScroll') ? bookList[this.pos].readScroll : 0;
            this.flag = false;
        }
        else if(prevState.loading !== this.state.loading){
            this.refs.box.scrollTop = 0;
        }
        let list =  document.querySelector('.chapterList .ant-modal-body');
        if (list !== null) {
            list.scrollTop = 45 * (this.index - 3);
        }

    }


    render() {
        return (
            <Spin className='loading' spinning={this.state.loading} tip="章节内容加载中">
                <Layout >
                    <Modal
                        className="chapterList"
                        title="Vertically centered modal dialog"
                        visible={this.state.chapterListShow}
                        onOk={() => this.showChapterList(false)}
                        onCancel={() => this.showChapterList(false)}
                    >
                        {
                            this.chapterList.map((item,index) => (<p id={index} className={parseInt(this.index,10) == index ?  'choosed' : ''} onClick={this.targetChapter} key={index}>{item.title}</p>))
                        }
                    </Modal>
                    {
                        this.state.show ? (() => {
                            return (
                                <Header className={styles.header}>
                                    <Link to="/"><Icon type="arrow-left" className={styles.pre}/></Link>
                                </Header>
                            )
                        })() : ''
                    }
                    <div ref='box' className={styles.box} style={this.state.readSetting} onClick={this.showSetting} onScroll={this.readScroll}>
                        {this.state.loading ? '' : (()=>{
                            return (
                                <div>
                                    <h3>{this.state.chapter.title}</h3>
                                    <p>{this.state.chapter.cpContent}</p>
                                    <h1 className={styles.control}>
                                        <span onClick={this.preChapter}>上一章</span>
                                        <span onClick={this.nextChapter}>下一章</span>
                                    </h1>
                                </div>
                            )
                        })()}
                    </div>
                    {
                        this.state.show ?  (() => {
                            return (
                                <Footer className={styles.footer}>
                                    <div
                                        className={styles.setting}
                                        tabIndex="100"
                                        onClick={this.readSettingShowControl}
                                        onBlur={this.readSettingShowControl}>
                                        <Icon type="setting" /><br/>设置
                                        {
                                            this.state.readSettingShow ?
                                                (
                                                    <div onClick={(e) => e.stopPropagation()}>
                                                        <div className={styles.font}>
                                                            <span onClick={this.fontDown}>Aa -</span>
                                                            <span onClick={this.fontUp}>Aa +</span>
                                                        </div>
                                                        <div className={styles.color}>
                                                            <i onClick={this.changeBackgroudColor} style={{backgroundColor: 'rgb(196,196)'}}></i>
                                                            <i onClick={this.changeBackgroudColor} style={{backgroundColor: 'rgb(162,157,137)'}}></i>
                                                            <i onClick={this.changeBackgroudColor} style={{backgroundColor: 'rgb(173,200,169)'}}></i>
                                                        </div>
                                                    </div>
                                                ) : ''
                                        }
                                    </div>
                                    <div><Icon type="download"  onClick={this.downloadBook}/><br/>下载</div>
                                    <div onClick={() => this.showChapterList(true)}><Icon type="bars" /><br/>目录</div>
                                </Footer>
                            )
                        })() : ''
                    }

                </Layout>
            </Spin>
        )
    }
}
export default Read;

以上就是阅读器大概内容了,当然了实际操作纯react方式并不可取,可查看redux版本获取更多知识。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom