programing

React를 통한 빅리스트 퍼포먼스

css3 2023. 3. 1. 11:26

React를 통한 빅리스트 퍼포먼스

React에서 필터 가능한 목록을 구현하는 중입니다.리스트의 구조는 아래 그림과 같습니다.

여기에 이미지 설명 입력

전제

동작 방법에 대해서는, 다음과 같이 설명합니다.

  • 인 ""에 있습니다.Search★★★★★★ 。
  • 상태는 다음과 같습니다.
{visible : 부울,파일: 어레이,filtered : 어레이,query : 문자열,현재 선택된색인 : 정수}
  • files는 파일 경로를 포함하는 매우 큰 배열일 가능성이 있습니다(확실한 숫자는 엔트리가 됩니다).
  • filtered는 사용자가 2자 이상 입력한 후 필터링된 배열입니다.에, 그 로 보존하는 것에 , 하지만, 이 한 것은, 라고 하는 것입니다.
  • currentlySelectedIndex필터링된 목록에서 현재 선택된 요소의 인덱스입니다.

  • 가 '2'에 Input컴포넌트는가 필터링되고 의 각 """가 됩니다.Result

  • ★★Result컴포넌트는 쿼리와 부분적으로 일치한 전체 경로를 표시하고 있으며 경로의 부분 일치 부분이 강조 표시됩니다.컴포넌트의 .

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

  • [ Up ] the [ Down ]에서 가 []키 [Input입니다.currentlySelectedIndex filtered. ★★★★★★★★★★★★★★★★★,Result.

문제

에 나는 이것을 했다.filesReact의 개발 버전을 사용하여 모두 정상적으로 동작했습니다.

는 제가 '아예'를 할 때 .files만엔입력에 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뀌뀌했 、 ,,지렇렇것것 。

는 결국 는는 for for for a i i i i i i i i i i 。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의 개발 버전과 실제 버전 간의 현저한 차이는 정상입니까?

React가 목록을 관리하는 방법을 생각하면 이해하거나 잘못하고 있습니까?

2016년 11월 14일 갱신

마이클 잭슨의 프레젠테이션을 찾았습니다.그는 이 프레젠테이션과 매우 유사한 문제를 다루고 있습니다.https://youtu.be/7S8v8jfLb1Q?t=26m2s

이 솔루션은 아래의 AskarovBeknar가 제안한 솔루션과 매우 유사합니다.

업데이트 2018년 4월 14일

이것은 매우 인기 있는 질문으로, 원래의 질문으로부터 진행되어 온 것이므로, 상기의 비디오를 봐 주세요.가상 레이아웃을 파악하려면 , 「Resact Virtualized」라이브러리를 사용하는 것도 추천합니다.

이 질문에 대한 다른 많은 답변과 마찬가지로 주요 문제는 주요 이벤트를 필터링 및 처리할 때 DOM 내의 많은 요소를 렌더링하는 데 시간이 걸린다는 것입니다.

문제의 원인이 되고 있는 React에 대해 본질적으로 잘못된 것은 아니지만, 퍼포먼스와 관련된 많은 문제와 마찬가지로 UI도 큰 비중을 차지할 수 있습니다.

UI가 효율성을 염두에 두고 설계되지 않으면 React와 같은 고성능 도구도 어려움을 겪을 수 있습니다.

결과 세트를 필터링하는 것은 @Koen이 말한 것처럼 훌륭한 시작입니다.

저는 아이디어를 조금 가지고 이런 문제에 어떻게 대처해야 하는지를 보여주는 예시를 만들었습니다.

은 결코 production ready코드는 적절한 컨셉을 나타내고 있어 보다 견고하게 변경할 수 있습니다.코드를 봐주세요....

리액트 라지리스트 리액트

여기에 이미지 설명 입력

비슷한 문제에 대한 제 경험으로는 DOM에 한 번에 100~200개 이상의 컴포넌트가 있을 경우 대응이 매우 어려워진다는 것입니다.으로써) 가 필요한 shouldComponentUpdate방법) 재시험에서 한두 가지 컴포넌트만 변경해도 여전히 상처받는 상황에 놓입니다.

현시점에서의 반응이 느린 것은 가상 DOM과 실제 DOM의 차이를 비교할 때입니다.수천 개의 컴포넌트가 있지만 몇 개만 업데이트해도 상관없습니다.리액션은 여전히 DOM 간에 큰 차이가 있습니다.

현재 페이지를 작성할 때 컴포넌트 수를 최소화하도록 설계하려고 합니다.컴포넌트 목록을 많이 렌더링할 때 이 작업을 수행하는 방법 중 하나는 다음과 같습니다.뭐...많은 컴포넌트 목록을 렌더링하지 않습니다.

즉, 현재 표시할 수 있는 컴포넌트만 렌더링하고 아래로 스크롤할수록 더 많은 컴포넌트를 렌더링합니다.사용자는 수천 개의 컴포넌트를 스크롤하여 아래로 스크롤하지 않습니다.그러길 바랍니다.

이를 위한 훌륭한 라이브러리는 다음과 같습니다.

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

사용법을 설명하면 다음과 같습니다.

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

페이지 맨 위에 있는 컴포넌트는 삭제되지 않기 때문에 스크롤을 오래 하면 퍼포먼스 문제가 재발하기 시작합니다.

링크를 해답으로 제공하는 것이 좋은 방법은 아니지만, 여기서 설명하는 것보다 이 라이브러리를 훨씬 더 잘 사용하는 방법에 대해 설명합니다.빅리스트가 나쁜 이유는 설명했지만 해결 방법도 설명했길 바랍니다.

우선 React의 개발 버전과 실제 버전의 차이가 큽니다. 프로덕션에서는 프로펠러 유형 검증과 같은 바이패스된 건전성 검사가 많이 있기 때문입니다.

그렇다면 Redux를 사용하는 것은 필요한 것(또는 플럭스 구현)에 매우 도움이 되므로 재고해 보는 것이 좋다고 생각합니다.이 프레젠테이션을 꼭 봐주세요.Big List High Performance React & Redux

그러나 redex에 들어가기 전에 컴포넌트를 작은 컴포넌트로 분할하여 리액트 코드를 수정해야 합니다.는 아이 렌더링을 완전히 생략하기 때문입니다.

보다 세분화된 구성 요소가 있는 경우 redux 및 react-redux를 사용하여 상태를 처리하여 데이터 흐름을 보다 효율적으로 구성할 수 있습니다.

최근 1,000개의 행을 렌더링해야 하고 각 행을 내용을 편집하여 수정할 수 있어야 하는 유사한 문제에 직면했습니다.이 미니 앱은 중복 콘서트가 있을 가능성이 있는 콘서트 목록을 표시하며, 체크박스를 켜고, 필요에 따라 콘서트 이름을 편집하여 중복될 가능성이 있는 콘서트를 오리지널 콘서트(복제가 아닌)로 표시하려는 경우 각각의 중복될 수 있는 콘서트 목록을 선택해야 합니다.특정 중복 가능성이 있는 아이템에 대해 아무것도 하지 않으면 중복으로 간주되어 삭제됩니다.

다음은 예를 제시하겠습니다.

여기에 이미지 설명 입력

기본적으로 4개의 메인 컴포넌트가 있습니다(여기에는 1개의 행만 있지만 예제를 위한 것입니다).

여기에 이미지 설명 입력

redux, react-redux, 불변, reselect, reselect, reselect 및 recompose를 사용한 풀코드(작업용 CodePen: Ligest List with 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')
);

이 미니 앱을 통해 얻은 교훈은 대용량 데이터셋으로 작업하는 경우입니다.

  • 리액션 컴포넌트는 작게 유지했을 때 가장 잘 동작합니다.
  • reselect는 재계산을 피하고 동일한 인수에 동일한 참조 객체(불변.js를 사용하는 경우)를 유지하는 데 매우 유용합니다.
  • connect컴포넌트가 하지 않는 소품만 중
  • "mapDispatchToProps"에서 된 ownProps 재탕을 위해
  • 리액션과 리덕스가 확실히 함께!

코멘트에서도 언급했듯이, 사용자가 브라우저에 있는 10000개의 결과를 한꺼번에 볼 필요는 없을 것 같습니다.

결과를 페이지화하여 항상 10개의 결과 목록만 표시하면 어떨까요?

는 Redux와 같은 다른 라이브러리를 사용하지 않고 이 기술을 사용하여 예를 만들었습니다.현재 키보드 네비게이션만으로 가능하지만 스크롤 작업도 쉽게 확장할 수 있습니다.

이 예제는 컨테이너 애플리케이션, 검색 구성 요소 및 목록 구성 요소의 세 가지 구성 요소로 구성됩니다.거의 모든 논리가 컨테이너 구성요소로 이동되었습니다.

요점은 을 추적하는 데 있다.startselected결과 및 키보드 상호 작용으로 전환합니다.

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>
  )
}

완전한 동작 예를 포함한 Fidle:https://jsfiddle.net/koenpunt/hm1xnpqk/

React 구성 요소에 로드하기 전에 필터를 사용해 보고 구성 요소에 적절한 양의 항목만 표시하고 필요에 따라 더 로드하십시오.한 번에 그렇게 많은 아이템을 볼 수 있는 사람은 없어요.

그런 것 같진 않지만 인덱스를 키로 사용하지 마세요.

개발 버전과 실제 버전이 다른 진짜 이유를 알아보려면profiling코드를 입력합니다.

페이지를 로드하고, 녹음을 시작하고, 변경을 수행하고, 녹화를 중지한 후, 타이밍을 클릭합니다.Chrome에서의 성능 프로파일링에 대한 지침은 여기를 참조하십시오.

  1. 개발 버전 체크에서는 각 컴포넌트의 프로프타입을 체크하여 개발 프로세스를 용이하게 하고, 실가동에서는 생략합니다.

  2. 문자열 목록을 필터링하는 작업은 모든 키업에서 매우 많은 비용이 듭니다.JavaScript의 단일 스레드 특성으로 인해 성능 문제가 발생할 수 있습니다.해결 방법은 데바운스 방식을 사용하여 지연이 만료될 때까지 필터 기능의 실행을 지연시키는 것입니다.

  3. 또 다른 문제는 방대한 목록 자체일 수 있습니다.가상 레이아웃을 생성하고 데이터를 교체하는 것만으로 생성된 항목을 재사용할 수 있습니다.기본적으로 고정된 높이로 스크롤 가능한 컨테이너 구성요소를 작성하고 그 안에 목록 컨테이너를 배치합니다.목록 컨테이너의 높이는 표시되는 목록 길이에 따라 수동으로 설정해야 합니다(itemHeight * numberOfItems). 스크롤 막대가 작동합니다.그런 다음 스크롤 가능한 컨테이너 높이를 채우고 한두 개의 연속 목록 효과를 모방할 수 있도록 항목 구성 요소를 만듭니다. 절대 위치를 지정하고 스크롤에서 연속 목록을 모방할 수 있도록 위치를 이동합니다(실장 방법에 대해 알아보겠습니다).

  4. 또 하나, DOM 에의 기입은, 특히 잘못했을 경우, 고비용의 조작이기도 합니다.캔버스를 사용하여 목록을 표시하고 스크롤에서 원활한 환경을 만들 수 있습니다.반응 캔버스 구성 요소를 확인합니다.나는 그들이 이미 리스트에 대해 몇 가지 작업을 했다고 들었다.

React Virtualized Select는 이 문제를 해결하기 위해 설계되었으며 제 경험상 매우 뛰어난 성능을 발휘합니다.설명부터:

react-virtualized 및 react-select를 사용하여 드롭다운에 많은 옵션 목록을 표시하는 HOC

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

이 문제로 어려움을 겪고 있는 분들을 위해 최대 100만 개의 레코드를 처리할 수 있는 컴포넌트를 작성했습니다.

게다가 다음과 같은 화려한 추가 기능도 탑재되어 있습니다.

  • 정렬
  • 캐싱
  • 커스텀 필터링
  • ...

많은 앱에서 프로덕션에서 사용하고 있으며, 매우 잘 작동합니다.

는 React를 권장하고 .react-window라이브러리: https://www.npmjs.com/package/react-window

보다 react-vitualized.

최근에 리액트용 멀티 셀렉트 입력을 개발하여 48,000개의 레코드로 테스트했습니다.문제없이 잘 되고 있어요.

https://www.npmjs.com/package/react-multi-select-advanced

내 시도는 이렇다.

react-syslogc-syslog-list OR, https://github.com/sawrozpdl/react-async-lazy-list

한번 시도해 보세요.윈도우 설정/가상화를 사용하고 로딩이 느리고 완전한 커스터마이즈가 가능하기 때문에 매우 빠릅니다.

언급URL : https://stackoverflow.com/questions/38033442/big-list-performance-with-react