ประสิทธิภาพรายการใหญ่พร้อม React


86

ฉันอยู่ในขั้นตอนการใช้รายการที่กรองได้ด้วย React โครงสร้างของรายการดังที่แสดงในภาพด้านล่าง

ป้อนคำอธิบายภาพที่นี่

พรีเมี่ยม

นี่คือคำอธิบายวิธีการทำงาน:

  • สถานะอยู่ในองค์ประกอบระดับสูงสุดคือSearchองค์ประกอบ
  • รัฐอธิบายดังนี้:
{
    มองเห็นได้: บูลีน
    ไฟล์: อาร์เรย์
    กรอง: อาร์เรย์
    แบบสอบถาม: สตริง,
    currentSelectedIndex: จำนวนเต็ม
}
  • files เป็นอาร์เรย์ที่มีพา ธ ไฟล์ที่มีขนาดใหญ่มาก (10,000 รายการเป็นจำนวนที่น่าเชื่อถือ)
  • filteredคืออาร์เรย์ที่กรองหลังจากผู้ใช้พิมพ์อย่างน้อย 2 อักขระ ฉันรู้ว่ามันเป็นข้อมูลอนุพันธ์และด้วยเหตุนี้จึงสามารถโต้แย้งเกี่ยวกับการจัดเก็บข้อมูลในสถานะได้ แต่จำเป็นสำหรับ
  • currentlySelectedIndex ซึ่งเป็นดัชนีขององค์ประกอบที่เลือกในปัจจุบันจากรายการที่กรองแล้ว

  • ผู้ใช้พิมพ์ตัวอักษรมากกว่า 2 ตัวลงในInputส่วนประกอบอาร์เรย์จะถูกกรองและสำหรับแต่ละรายการในอาร์เรย์ที่กรองResultองค์ประกอบจะถูกแสดงผล

  • แต่ละResultองค์ประกอบจะแสดงเส้นทางแบบเต็มที่ตรงกับคำค้นหาบางส่วนและส่วนที่ตรงกันบางส่วนของเส้นทางจะถูกเน้น ตัวอย่างเช่น DOM ของคอมโพเนนต์ผลลัพธ์หากผู้ใช้พิมพ์ 'le' จะเป็นดังนี้:

    <li>this/is/a/fi<strong>le</strong>/path</li>

  • หากผู้ใช้กดปุ่มขึ้นหรือลงในขณะที่Inputคอมโพเนนต์เน้นการcurrentlySelectedIndexเปลี่ยนแปลงตามfilteredอาร์เรย์ ทำให้Resultองค์ประกอบที่ตรงกับดัชนีถูกทำเครื่องหมายว่าเลือกไว้ทำให้เกิดการแสดงผลซ้ำ

ปัญหา

ตอนแรกฉันทดสอบสิ่งนี้ด้วยอาร์เรย์ที่เล็กพอfilesโดยใช้ React เวอร์ชันพัฒนาและทุกอย่างทำงานได้ดี

ปัญหาเกิดขึ้นเมื่อฉันต้องจัดการกับfilesอาร์เรย์ที่ใหญ่ถึง 10,000 รายการ การพิมพ์ตัวอักษร 2 ตัวในอินพุตจะสร้างรายการขนาดใหญ่และเมื่อฉันกดปุ่มขึ้นและลงเพื่อนำทางมันจะล่าช้ามาก

ในตอนแรกฉันไม่มีองค์ประกอบที่กำหนดไว้สำหรับResultองค์ประกอบและฉันเป็นเพียงการสร้างรายการได้ทันทีในแต่ละการเรนเดอร์ของSearchองค์ประกอบดังต่อไปนี้:

results  = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query;

     matchIndex = file.indexOf(match);
     start = file.slice(0, matchIndex);
     end = file.slice(matchIndex + match.length);

     return (
         <li onClick={this.handleListClick}
             data-path={file}
             className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
             key={file} >
             {start}
             <span className="marked">{match}</span>
             {end}
         </li>
     );
}.bind(this));

ดังที่คุณทราบทุกครั้งที่มีการcurrentlySelectedIndexเปลี่ยนแปลงจะทำให้เกิดการแสดงผลซ้ำและรายการจะถูกสร้างขึ้นใหม่ทุกครั้ง ฉันคิดว่าเนื่องจากฉันตั้งkeyค่าให้กับแต่ละliองค์ประกอบการตอบสนองจะหลีกเลี่ยงการแสดงผลliองค์ประกอบอื่น ๆ ทั้งหมดที่ไม่มีการclassNameเปลี่ยนแปลง แต่ดูเหมือนว่าจะไม่เป็นเช่นนั้น

ฉันลงเอยด้วยการกำหนดคลาสสำหรับResultองค์ประกอบโดยที่มันจะตรวจสอบอย่างชัดเจนว่าแต่ละResultองค์ประกอบควรแสดงผลอีกครั้งหรือไม่โดยขึ้นอยู่กับว่าก่อนหน้านี้ถูกเลือกหรือไม่และขึ้นอยู่กับอินพุตของผู้ใช้ปัจจุบัน:

var ResultItem = React.createClass({
    shouldComponentUpdate : function(nextProps) {
        if (nextProps.match !== this.props.match) {
            return true;
        } else {
            return (nextProps.selected !== this.props.selected);
        }
    },
    render : function() {
        return (
            <li onClick={this.props.handleListClick}
                data-path={this.props.file}
                className={
                    (this.props.selected) ? "valid selected" : "valid"
                }
                key={this.props.file} >
                {this.props.children}
            </li>
        );
    }
});

และรายการจะถูกสร้างขึ้นในขณะนี้:

results = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query, selected;

    matchIndex = file.indexOf(match);
    start = file.slice(0, matchIndex);
    end = file.slice(matchIndex + match.length);
    selected = (index === this.state.currentlySelected) ? true : false

    return (
        <ResultItem handleClick={this.handleListClick}
            data-path={file}
            selected={selected}
            key={file}
            match={match} >
            {start}
            <span className="marked">{match}</span>
            {end}
        </ResultItem>
    );
}.bind(this));
}

สิ่งนี้ทำให้ประสิทธิภาพดีขึ้นเล็กน้อยแต่ก็ยังไม่ดีพอ สิ่งนี้คือตอนที่ฉันทดสอบกับสิ่งที่ตอบสนองเวอร์ชันที่ใช้งานจริงนั้นทำงานได้อย่างราบรื่นไม่มีความล่าช้าเลย

ด้านล่าง

ความแตกต่างที่เห็นได้ชัดเจนระหว่าง React เวอร์ชันพัฒนาและเวอร์ชันที่ใช้งานจริงเป็นเรื่องปกติหรือไม่

ฉันเข้าใจ / ทำอะไรผิดหรือเปล่าเมื่อฉันคิดว่า React จัดการรายการอย่างไร

อัพเดท 14-11-2559

ฉันได้พบการนำเสนอของ Michael Jackson ซึ่งเขาจัดการกับปัญหาที่คล้ายกับเรื่องนี้มาก: https://youtu.be/7S8v8jfLb1Q?t=26m2s

วิธีแก้ปัญหาคล้ายกับที่เสนอโดยคำตอบของ AskarovBeknar ด้านล่าง

อัพเดท 14-4-2018

เนื่องจากเห็นได้ชัดว่านี่เป็นคำถามยอดนิยมและสิ่งต่าง ๆ ได้ดำเนินไปแล้วเนื่องจากคำถามเดิมถูกถามในขณะที่ฉันขอแนะนำให้คุณดูวิดีโอที่เชื่อมโยงด้านบนเพื่อให้เข้าใจรูปแบบเสมือนจริงฉันขอแนะนำให้คุณใช้React Virtualizedห้องสมุดหากคุณไม่ต้องการประดิษฐ์วงล้อใหม่


คุณหมายถึงอะไรจากการตอบสนองเวอร์ชันพัฒนา / การผลิต?
Dibesjr


ฉันเห็นแล้วขอบคุณ เพื่อที่จะตอบคำถามของคุณข้อหนึ่งกล่าวว่ามีความคลาดเคลื่อนในการเพิ่มประสิทธิภาพระหว่างเวอร์ชัน สิ่งหนึ่งที่ต้องระวังในรายการใหญ่คือการสร้างฟังก์ชันในการเรนเดอร์ของคุณ จะมีผลงานยอดเยี่ยมเมื่อคุณเข้าสู่รายการยักษ์ใหญ่ ฉันจะลองดูว่าใช้เวลานานแค่ไหนในการสร้างรายการนั้นโดยใช้เครื่องมือที่สมบูรณ์แบบfacebook.github.io/react/docs/perf.html
Dibesjr

2
ฉันคิดว่าคุณควรพิจารณาใช้ Redux ใหม่เพราะเป็นสิ่งที่คุณต้องการที่นี่ (หรือการใช้งานฟลักซ์ประเภทใดก็ได้) คุณควรดูงานนำเสนอนี้อย่างชัดเจน: Big List High Performance React & Redux
Pierre Criulanscy

2
ฉันสงสัยว่าผู้ใช้จะได้รับประโยชน์จากการเลื่อนดูผลลัพธ์ 10,000 รายการ แล้วจะเกิดอะไรขึ้นถ้าคุณแสดงผลเพียง 100 อันดับแรกหรือมากกว่านั้นและอัปเดตตามคำค้นหา
โคเอ็น.

คำตอบ:


18

เช่นเดียวกับคำตอบอื่น ๆ สำหรับคำถามนี้ปัญหาหลักอยู่ที่ความจริงที่ว่าการแสดงผลองค์ประกอบจำนวนมากใน DOM ในขณะที่การกรองและจัดการเหตุการณ์สำคัญนั้นจะช้า

คุณไม่ได้ทำอะไรผิดโดยเนื้อแท้เกี่ยวกับ React ที่เป็นสาเหตุของปัญหา แต่เช่นเดียวกับปัญหาหลาย ๆ อย่างที่เกี่ยวข้องกับประสิทธิภาพ UI อาจทำให้เกิดความเสียหายได้มาก

หาก UI ของคุณไม่ได้รับการออกแบบโดยคำนึงถึงประสิทธิภาพแม้แต่เครื่องมืออย่าง React ที่ออกแบบมาให้มีประสิทธิภาพก็จะได้รับผลกระทบ

การกรองชุดผลลัพธ์เป็นการเริ่มต้นที่ดีตามที่ @Koen กล่าวไว้

ฉันเล่นกับแนวคิดนี้เล็กน้อยและสร้างแอปตัวอย่างที่แสดงให้เห็นว่าฉันจะเริ่มจัดการกับปัญหาประเภทนี้ได้อย่างไร

นี่ไม่ใช่production readyรหัส แต่มันแสดงให้เห็นถึงแนวคิดอย่างเพียงพอและสามารถแก้ไขให้มีประสิทธิภาพมากขึ้นอย่าลังเลที่จะดูรหัส - ฉันหวังว่าอย่างน้อยที่สุดมันก็ให้แนวคิดบางอย่างแก่คุณ ... ;)

ตอบสนองขนาดใหญ่รายการตัวอย่าง

ป้อนคำอธิบายภาพที่นี่


1
ฉันรู้สึกแย่จริงๆที่ต้องเลือกคำตอบเพียงคำตอบเดียวดูเหมือนว่าพวกเขาจะมีความพยายาม แต่ตอนนี้ฉันอยู่ในช่วงพักร้อนโดยไม่มีพีซีและไม่สามารถตรวจสอบพวกเขาด้วยความสนใจที่สมควรได้ ฉันเลือกอันนี้เพราะมันสั้นพอและตรงประเด็นเข้าใจได้แม้อ่านจากโทรศัพท์ ฉันรู้เหตุผลง่อย ๆ
Dimitris Karagiannis

แก้ไขไฟล์โฮสต์หมายความว่า127.0.0.1 * http://localhost:3001อย่างไร
stackjlei

@stackjlei ฉันคิดว่าเขาหมายถึงการแมป 127.0.0.1 กับlocalhost: 3001ใน / etc / hosts
Maverick

16

ประสบการณ์ของฉันกับปัญหาที่คล้ายกันมากคือการตอบสนองจะทนทุกข์ทรมานจริงๆหากมีส่วนประกอบมากกว่า 100-200 รายการใน DOM แม้ว่าคุณจะระมัดระวังเป็นอย่างยิ่ง (โดยการตั้งค่าคีย์และ / หรือใช้shouldComponentUpdateวิธีการทั้งหมด) เพื่อเปลี่ยนส่วนประกอบหนึ่งหรือสองส่วนในการเรนเดอร์ซ้ำคุณก็ยังคงอยู่ในโลกแห่งความเจ็บปวด

ส่วนที่ช้าของการตอบสนองในขณะนี้คือเมื่อเปรียบเทียบความแตกต่างระหว่าง DOM เสมือนกับ DOM จริง หากคุณมีส่วนประกอบหลายพันรายการ แต่อัปเดตเพียงไม่กี่รายการก็ไม่สำคัญการตอบสนองยังคงมีการดำเนินการที่แตกต่างกันอย่างมากระหว่าง DOM

เมื่อฉันเขียนเพจตอนนี้ฉันพยายามออกแบบให้ลดจำนวนคอมโพเนนต์วิธีหนึ่งที่ทำได้เมื่อแสดงรายการคอมโพเนนต์จำนวนมากคือ ... ดี ... ไม่แสดงรายการคอมโพเนนต์จำนวนมาก

สิ่งที่ฉันหมายถึงคือ: แสดงเฉพาะส่วนประกอบที่คุณสามารถเห็นได้ในปัจจุบันแสดงผลมากขึ้นเมื่อคุณเลื่อนลงคุณเป็นผู้ใช้ไม่น่าจะเลื่อนดูส่วนประกอบหลายพันรายการด้วยวิธีใดก็ได้ .... ฉันหวังว่า

ห้องสมุดที่ยอดเยี่ยมสำหรับการทำสิ่งนี้คือ:

https://www.npmjs.com/package/react-infinite-scroll

ด้วยวิธีการที่ยอดเยี่ยมที่นี่:

http://www.reactexamples.com/react-infinite-scroll/

ฉันเกรงว่าจะไม่ลบส่วนประกอบที่อยู่ด้านบนสุดของหน้าดังนั้นหากคุณเลื่อนนานพอที่จะเกิดปัญหาด้านประสิทธิภาพจะเริ่มกลับมาอีกครั้ง

ฉันรู้ว่าการให้ลิงค์เป็นคำตอบไม่ใช่แนวทางปฏิบัติที่ดี แต่ตัวอย่างที่ให้ไว้จะอธิบายวิธีใช้ไลบรารีนี้ได้ดีกว่าที่ฉันทำได้ที่นี่ หวังว่าฉันจะอธิบายได้ว่าทำไมรายการใหญ่ถึงไม่ดี แต่ก็เป็นวิธีแก้ปัญหาด้วย


2
อัปเดต: แพ็คเกจที่อยู่ในคำตอบนี้ไม่ได้รับการดูแล ส้อมถูกตั้งค่าบนnpmjs.com/package/react-infinite-scroller
Ali Al Amine

11

ประการแรกความแตกต่างระหว่างการพัฒนาและเวอร์ชันที่ใช้งานจริงของ React นั้นมีมากเนื่องจากในการผลิตมีการตรวจสอบความถูกต้องโดยผ่านการตรวจสอบจำนวนมาก (เช่นการตรวจสอบประเภทเสา)

จากนั้นฉันคิดว่าคุณควรพิจารณาใหม่โดยใช้ Redux เพราะจะมีประโยชน์อย่างยิ่งที่นี่สำหรับสิ่งที่คุณต้องการ (หรือการใช้งานฟลักซ์ประเภทใดก็ได้) คุณแน่นอนควรจะดูที่นำเสนอนี้: รายการบิ๊กประสิทธิภาพสูง React & Redux

แต่ก่อนที่จะดำน้ำใน Redux คุณจะต้องทำ ajustements บางอย่างเพื่อตอบสนองรหัสโดยแยกส่วนประกอบของคุณเป็นส่วนประกอบที่มีขนาดเล็กเพราะจะโดยสิ้นเชิงบายพาสการแสดงผลของเด็กจึงเป็นกำไรมากshouldComponentUpdate

เมื่อคุณมีส่วนประกอบที่ละเอียดมากขึ้นคุณสามารถจัดการกับสถานะด้วย redux และ react-redux เพื่อจัดระเบียบการไหลของข้อมูลได้ดีขึ้น

เมื่อเร็ว ๆ นี้ฉันประสบปัญหาคล้าย ๆ กันเมื่อต้องแสดงผลหนึ่งพันแถวและสามารถแก้ไขแต่ละแถวได้โดยแก้ไขเนื้อหา มินิแอปนี้จะแสดงรายการคอนเสิร์ตที่มีคอนเสิร์ตซ้ำกันและฉันจำเป็นต้องเลือกสำหรับแต่ละรายการที่อาจซ้ำกันหากฉันต้องการทำเครื่องหมายรายการที่อาจซ้ำกันเป็นคอนเสิร์ตดั้งเดิม (ไม่ใช่รายการที่ซ้ำกัน) โดยทำเครื่องหมายในช่องและหากจำเป็นให้แก้ไข ชื่อคอนเสิร์ต. หากฉันไม่ดำเนินการใด ๆ กับรายการที่อาจซ้ำกันโดยเฉพาะรายการนั้นจะถือว่าซ้ำกันและจะถูกลบออก

นี่คือสิ่งที่ดูเหมือน:

ป้อนคำอธิบายภาพที่นี่

โดยทั่วไปมีส่วนประกอบหลัก 4 ส่วน (มีเพียงแถวเดียวที่นี่ แต่เพื่อประโยชน์ในตัวอย่าง):

ป้อนคำอธิบายภาพที่นี่

นี่คือรหัสแบบเต็ม (CodePen ที่ใช้งานได้: รายการใหญ่พร้อม React & Redux ) โดยใช้redux , react-redux , ไม่เปลี่ยนรูป , เลือกใหม่และจัดองค์ประกอบใหม่ :

const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ })

const types = {
    CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED',
    CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED',
};

const changeName = (pk, name) => ({
    type: types.CONCERTS_DEDUP_NAME_CHANGED,
    pk,
    name
});

const toggleConcert = (pk, toggled) => ({
    type: types.CONCERTS_DEDUP_CONCERT_TOGGLED,
    pk,
    toggled
});


const reducer = (state = initialState, action = {}) => {
    switch (action.type) {
        case types.CONCERTS_DEDUP_NAME_CHANGED:
            return state
                .updateIn(['names', String(action.pk)], () => action.name)
                .set('_state', 'not_saved');
        case types.CONCERTS_DEDUP_CONCERT_TOGGLED:
            return state
                .updateIn(['concerts', String(action.pk)], () => action.toggled)
                .set('_state', 'not_saved');
        default:
            return state;
    }
};

/* configureStore */
const store = Redux.createStore(
    reducer,
    initialState
);

/* SELECTORS */

const getDuplicatesGroups = (state) => state.get('duplicatesGroups');

const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]);

const getConcerts = (state) => state.get('concerts');

const getNames = (state) => state.get('names');

const getConcertName = (state, pk) => getNames(state).get(String(pk));

const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk));

const getGroupNames = reselect.createSelector(
    getDuplicatesGroups,
    (duplicates) => duplicates.flip().toList()
);

const makeGetConcertName = () => reselect.createSelector(
    getConcertName,
    (name) => name
);

const makeIsConcertOriginal = () => reselect.createSelector(
    isConcertOriginal,
    (original) => original
);

const makeGetDuplicateGroup = () => reselect.createSelector(
    getDuplicateGroup,
    (duplicates) => duplicates
);



/* COMPONENTS */

const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => {
    return (
        <tr>
            <td>{name}</td>
            <DuplicatesRowColumn name={name}/>
        </tr>
    )
});

const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => (
    <input type="checkbox" defaultChecked={toggled} {...otherProps}/>
));


/* CONTAINERS */

let DuplicatesTable = ({ groups }) => {

    return (
        <div>
            <table className="pure-table pure-table-bordered">
                <thead>
                    <tr>
                        <th>{'Concert'}</th>
                        <th>{'Duplicates'}</th>
                    </tr>
                </thead>
                <tbody>
                    {groups.map(name => (
                        <DuplicatesTableRow key={name} name={name} />
                    ))}
                </tbody>
            </table>
        </div>
    )

};

DuplicatesTable.propTypes = {
    groups: React.PropTypes.instanceOf(Immutable.List),
};

DuplicatesTable = ReactRedux.connect(
    (state) => ({
        groups: getGroupNames(state),
    })
)(DuplicatesTable);


let DuplicatesRowColumn = ({ duplicates }) => (
    <td>
        <ul>
            {duplicates.map(d => (
                <DuplicateItem
                    key={d}
                    pk={d}/>
            ))}
        </ul>
    </td>
);

DuplicatessRowColumn.propTypes = {
    duplicates: React.PropTypes.arrayOf(
        React.PropTypes.string
    )
};

const makeMapStateToProps1 = (_, { name }) => {
    const getDuplicateGroup = makeGetDuplicateGroup();
    return (state) => ({
        duplicates: getDuplicateGroup(state, name)
    });
};

DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn);


let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => {
    return (
        <li>
            <table>
                <tbody>
                    <tr>
                        <td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td>
                        <td>
                            <PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/>
                        </td>
                    </tr>
                </tbody>
            </table>
        </li>
    )
}

const makeMapStateToProps2 = (_, { pk }) => {
    const getConcertName = makeGetConcertName();
    const isConcertOriginal = makeIsConcertOriginal();

    return (state) => ({
        name: getConcertName(state, pk),
        toggled: isConcertOriginal(state, pk)
    });
};

DuplicateItem = ReactRedux.connect(
    makeMapStateToProps2,
    (dispatch) => ({
        onNameChange(pk, name) {
            dispatch(changeName(pk, name));
        },
        onToggle(pk, toggled) {
            dispatch(toggleConcert(pk, toggled));
        }
    })
)(DuplicateItem);


const App = () => (
    <div style={{ maxWidth: '1200px', margin: 'auto' }}>
        <DuplicatesTable />
    </div>
)

ReactDOM.render(
    <ReactRedux.Provider store={store}>
        <App/>
    </ReactRedux.Provider>,
    document.getElementById('app')
);

บทเรียนที่ได้เรียนรู้จากการทำมินิแอปนี้เมื่อทำงานกับชุดข้อมูลขนาดใหญ่

  • ส่วนประกอบที่ทำปฏิกิริยาจะทำงานได้ดีที่สุดเมื่อมีขนาดเล็ก
  • การเลือกใหม่จะมีประโยชน์มากเพื่อหลีกเลี่ยงการคำนวณซ้ำและเก็บอ็อบเจ็กต์อ้างอิงเดียวกันไว้ (เมื่อใช้ไม่เปลี่ยนรูป js) โดยมีอาร์กิวเมนต์เดียวกัน
  • สร้างconnectองค์ประกอบ ed สำหรับส่วนประกอบที่ใกล้เคียงที่สุดของข้อมูลที่ต้องการเพื่อหลีกเลี่ยงไม่ให้ส่วนประกอบส่งผ่านอุปกรณ์ประกอบฉากที่ไม่ได้ใช้เท่านั้น
  • การใช้ฟังก์ชัน fabric เพื่อสร้าง mapDispatchToProps เมื่อคุณต้องการเพียงเสาเริ่มต้นที่ให้มาเท่านั้นที่ownPropsจำเป็นเพื่อหลีกเลี่ยงการเรนเดอร์ที่ไร้ประโยชน์
  • ตอบสนองและลดแรงกระแทกด้วยกัน!

2
ฉันไม่คิดว่าการเพิ่มการพึ่งพาให้กับ redux เป็นสิ่งที่จำเป็นในการแก้ปัญหาของ OP การดำเนินการจัดส่งเพิ่มเติมสำหรับการกรองชุดผลลัพธ์ของเขาจะทำให้ปัญหาเพิ่มขึ้นเท่านั้นการจัดส่งไม่แพงอย่างที่คิดการจัดการกับสถานการณ์เฉพาะนี้ด้วยส่วนประกอบในพื้นที่ รัฐเป็นแนวทางที่มีประสิทธิภาพที่สุด
deowk

4
  1. การตอบสนองในเวอร์ชันการพัฒนาจะตรวจหา proptypes ของแต่ละองค์ประกอบเพื่อให้กระบวนการพัฒนาง่ายขึ้นในขณะที่การผลิตจะถูกละไว้

  2. การกรองรายการสตริงเป็นการดำเนินการที่มีราคาแพงมากสำหรับทุกการกดปุ่ม อาจทำให้เกิดปัญหาด้านประสิทธิภาพเนื่องจาก JavaScript แบบเธรดเดียว วิธีแก้ไขอาจใช้วิธีdebounceเพื่อชะลอการทำงานของฟังก์ชันตัวกรองของคุณจนกว่าความล่าช้าจะหมดอายุ

  3. ปัญหาอื่นอาจเป็นรายการใหญ่เอง คุณสามารถสร้างเค้าโครงเสมือนและนำรายการที่สร้างขึ้นมาใช้ใหม่ได้เพียงแค่แทนที่ข้อมูล โดยทั่วไปคุณจะสร้างส่วนประกอบคอนเทนเนอร์ที่เลื่อนได้โดยมีความสูงคงที่ซึ่งคุณจะวางคอนเทนเนอร์รายการ ควรตั้งค่าความสูงของคอนเทนเนอร์รายการด้วยตนเอง (itemHeight * numberOfItems) ขึ้นอยู่กับความยาวของรายการที่มองเห็นได้เพื่อให้แถบเลื่อนทำงาน จากนั้นสร้างส่วนประกอบรายการสองสามรายการเพื่อเติมเต็มความสูงของคอนเทนเนอร์ที่เลื่อนได้และอาจเพิ่มเอฟเฟกต์รายการเลียนแบบต่อเนื่องพิเศษหนึ่งหรือสองรายการ ทำให้ตำแหน่งที่แน่นอนและในการเลื่อนเพียงแค่ย้ายตำแหน่งเพื่อเลียนแบบรายการต่อเนื่อง (ฉันคิดว่าคุณจะพบวิธีการใช้งาน :)

  4. อีกสิ่งหนึ่งคือการเขียนถึง DOM ก็คือการดำเนินการที่มีราคาแพงเช่นกันโดยเฉพาะอย่างยิ่งถ้าคุณทำผิด คุณสามารถใช้แคนวาสเพื่อแสดงรายการและสร้างประสบการณ์ที่ราบรื่นในการเลื่อน ชำระเงินส่วนประกอบผ้าใบตอบสนอง ฉันได้ยินมาว่าพวกเขาทำงานในรายการไปแล้ว


ข้อมูลเกี่ยวกับReact in development? และเหตุใดจึงต้องตรวจสอบต้นแบบของแต่ละองค์ประกอบ?
Liuuil

4

ลองใช้ React Virtualized Select ซึ่งออกแบบมาเพื่อแก้ไขปัญหานี้และดำเนินการได้อย่างน่าประทับใจจากประสบการณ์ของฉัน จากคำอธิบาย:

HOC ที่ใช้ react-virtualized และ react-select เพื่อแสดงรายการตัวเลือกจำนวนมากในเมนูแบบเลื่อนลง

https://github.com/bvaughn/react-virtualized-select


4

เช่นเดียวกับที่ฉันกล่าวไว้ในความคิดเห็นของฉันฉันสงสัยว่าผู้ใช้ต้องการผลลัพธ์ทั้งหมด 10,000 รายการในเบราว์เซอร์พร้อมกัน

จะเกิดอะไรขึ้นถ้าคุณเลื่อนดูผลลัพธ์และแสดงรายการผลลัพธ์ 10 รายการเสมอ

ฉันได้สร้างตัวอย่างโดยใช้เทคนิคนี้โดยไม่ต้องใช้ไลบรารีอื่นเช่น Redux ขณะนี้มีเฉพาะการนำทางด้วยแป้นพิมพ์ แต่สามารถขยายให้ใช้งานได้อย่างง่ายดายในการเลื่อนด้วย

ตัวอย่างมีส่วนประกอบ 3 ส่วนแอปพลิเคชันคอนเทนเนอร์ส่วนประกอบการค้นหาและส่วนประกอบรายการ ตรรกะเกือบทั้งหมดถูกย้ายไปยังส่วนประกอบคอนเทนเนอร์

ส่วนสำคัญอยู่ที่การติดตามstartและselectedผลลัพธ์และเปลี่ยนสิ่งเหล่านั้นในการโต้ตอบกับแป้นพิมพ์

nextResult: function() {
  var selected = this.state.selected + 1
  var start = this.state.start
  if(selected >= start + this.props.limit) {
    ++start
  }
  if(selected + start < this.state.results.length) {
    this.setState({selected: selected, start: start})
  }
},

prevResult: function() {
  var selected = this.state.selected - 1
  var start = this.state.start
  if(selected < start) {
    --start
  }
  if(selected + start >= 0) {
    this.setState({selected: selected, start: start})
  }
},

ในขณะที่เพียงแค่ส่งไฟล์ทั้งหมดผ่านตัวกรอง:

updateResults: function() {
  var results = this.props.files.filter(function(file){
    return file.file.indexOf(this.state.query) > -1
  }, this)

  this.setState({
    results: results
  });
},

และหั่นผลลัพธ์ตามstartและlimitในrenderวิธีการ:

render: function() {
  var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit)
  return (
    <div>
      <Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} />
      <List files={files} selected={this.state.selected - this.state.start} />
    </div>
  )
}

ซอที่มีตัวอย่างการทำงานแบบเต็ม: https://jsfiddle.net/koenpunt/hm1xnpqk/


3

ลองกรองก่อนที่จะโหลดลงในคอมโพเนนต์ React และแสดงเฉพาะรายการในจำนวนที่เหมาะสมเท่านั้น ไม่มีใครสามารถดูหลายรายการในคราวเดียว

ผมไม่คิดว่าคุณจะอยู่ แต่ไม่ได้ใช้ดัชนีเป็นกุญแจ

หากต้องการทราบสาเหตุที่แท้จริงว่าทำไมเวอร์ชันระหว่างการพัฒนาและเวอร์ชันที่ใช้งานจริงจึงแตกต่างกันคุณสามารถลองใช้profilingโค้ดของคุณได้

โหลดหน้าของคุณเริ่มบันทึกดำเนินการเปลี่ยนแปลงหยุดบันทึกแล้วตรวจสอบเวลา ดูคำแนะนำสำหรับการทำโปรไฟล์ประสิทธิภาพใน Chromeได้ที่นี่


2

สำหรับใครก็ตามที่กำลังดิ้นรนกับปัญหานี้ฉันได้เขียนส่วนประกอบreact-big-listที่จัดการรายการได้มากถึง 1 ล้านรายการ

นอกจากนี้ยังมาพร้อมกับคุณสมบัติพิเศษบางอย่างเช่น:

  • การเรียงลำดับ
  • เก็บเอาไว้
  • การกรองแบบกำหนดเอง
  • ...

เราใช้มันในการผลิตในบางแอพและใช้งานได้ดี


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.