【问题标题】:React Native hooks, useRef and useEffectReact Native 钩子,useRef 和 useEffect
【发布时间】:2020-03-07 11:21:33
【问题描述】:

我正在尝试使用 React Native 将 Class 组件 转换为 Functional

我的Search 组件允许用户搜索电影名称,我正在调用 API 向他展示所有对应的电影。

这是我的类组件:

class Search extends React.Component {

    constructor(props) {
        super(props);
        this.searchedText = "";
        this.page = 0
        this.totalPages = 0
        this.state = {
            films: [],
            isLoading: false
        }
    }

    _loadFilms() {
        if (this.searchedText.length > 0) {
            this.setState({ isLoading: true })
            getFilmsFromApiWithSearchedText(this.searchedText, this.page+1).then(data => {
                this.page = data.page
                this.totalPages = data.total_pages
                this.setState({
                    films: [ ...this.state.films, ...data.results ],
                    isLoading: false
                })
            })
        }
    }

    _searchTextInputChanged(text) {
        this.searchedText = text
    }

    _searchFilms() {
        this.page = 0
        this.totalPages = 0
        this.setState({
            films: [],
        }, () => {
            this._loadFilms()
        })
    }

render() {
        return (
            <View style={styles.main_container}>
                <TextInput
                    style={styles.textinput}
                    placeholder='Titre du film'
                    onChangeText={(text) => this._searchTextInputChanged(text)}
                    onSubmitEditing={() => this._searchFilms()}
                />
                <Button title='Rechercher' onPress={() => this._searchFilms()}/>
                <FlatList
                    data={this.state.films}
                    keyExtractor={(item) => item.id.toString()}
                    renderItem={({item}) => <FilmItem film={item}/>}
                    onEndReachedThreshold={0.5}
                    onEndReached={() => {
                        if (this.page < this.totalPages) {
                            this._loadFilms()
                        }
                    }}
                />
            </View>
        )
    }
}
render() {
      return (
            <View>
                <TextInput
                    onChangeText={(text) => this._searchTextInputChanged(text)}
                    onSubmitEditing={() => this._searchFilms()}
                />
                <Button title='Search' onPress={() => this._searchFilms()}/>
                <FlatList
                    data={this.state.films}
                    keyExtractor={(item) => item.id.toString()}
                    renderItem={({item}) => <FilmItem film={item}/>}
                    onEndReachedThreshold={0.5}
                    onEndReached={() => {
                        if (this.page < this.totalPages) {
                            this._loadFilms()
                        }
                    }}
                />
                {this._displayLoading()}
            </View>
        )
    }
}

我如何用钩子翻译以下内容:

  • this.pagethis.totalPages ? useRef 是解决方案吗?

  • _searchFilms() 中,当我的电影列表为空时(因为这是一个新搜索),我正在使用setState 回调进行新的API 调用。但是之后立即执行是行不通的,因为setState 是异步的。 但我找不到用钩子做的方法。

我认为useEffect 可以这样做,但是:

  • 我只想在film 列表为空时调用此 API,因为我调用_searchFilms() 进行新搜索
  • _loadFilms() 在用户滚动时被调用以向 FlatList 添加更多电影(用于相同的搜索),因此在这种情况下我无法清除 this.films

这是我到目前为止的翻译方式:

const Search = () => {
    const [searchText, setSearchText] = useState('');
    const [films, setFilms] = useState([]);
    // handle pagination
    const page = useRef(0);
    const totalPages = useRef(0);
    // handle api fetch
    const [isLoading, setIsLoading] = useState(false);


    const loadFilmsFromApi = () => {
        getFilmsFromApiWithSearchedText(searchText, page + 1).then((data) => {
            page.current = data.page;
            totalPages.current = data.total_pages;
            setFilms(films => [...films, ...data.results]);
            setIsLoading(false);
        })
    };

    const searchFilm = () => {
        if (searchText.length > 0) {
            page.current = 0;
            totalPages.current = 0;
            setFilms([]);
            // HERE MY Films list won't be cleared (setState asynchronous)
            loadFilmsFromApi();

            // after the api call, clear input
            setSearchText('');
        }
    };

    useEffect(() => {
        console.log(page, totalPages, "Film number" + films.length);
    }, [films]);

【问题讨论】:

    标签: reactjs react-native react-hooks use-effect


    【解决方案1】:

    我认为你走在正确的道路上。至于totalPagespage,如果您想在不同的渲染之间保持该值(设置状态时),将其作为ref 是有意义的

    const Search = () => {
    
      const [searchText, setSearchText] = useState('');
      const [films, setFilms] = useState([]);
      // handle pagination
      const page = useRef(0);
      const totalPages = useRef(0);
      // handle api fetch
      const [isLoading, setIsLoading] = useState(false);
      
      // This can be invoked by either search or user scroll
      // When pageNum is undefined, it means it is triggered by search
      const loadFilmsFromApi = (pageNum) => {
        console.log("APPEL", 'loadFills');
        getFilmsFromApiWithSearchedText(searchText, pageNum ? pageNum + 1 : 1).then((data) => {
          page.current = data.page;
          totalPages.current = data.total_pages;
          
          setFilms(films => {
            if(pageNum) {
              return [...films, ...data.results];
            } else {
              return [data.results];
            }
          });
          
          setIsLoading(false);
        })
      };
      
      useEffect(() => {
       if (searchText.length > 0) {
          page.current = 0;
          totalPages.current = 0;
          
          setFilms([]);
          loadFilmsFromApi();
          // after the api call, clear input
          setSearchText('');
        }
      
      }, [searchText, loadFilmsFromApi]);
    
    
      useEffect(() => {
        console.log(page, totalPages, "Nombre de film " + films.length);
      }, [films]);
    
      return ( <
        div > Search < /div>
      );
    
    };
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

    【讨论】:

    • “如果您想在不同的渲染之间保持该值,那么引用是有意义的” - 令人尴尬的是,我不知道使用 useRef 时这些值会在渲染之间保持不变。谢谢你
    • @Chev 凡事都有第一次。您只有在开始使用它们后才会了解
    • 我添加了更多关于我的组件如何工作的解释。问题是当我调用setFilms([]) 然后进行API 调用时,films 将不清楚,因为setState 是异步的。为什么你在数组中传递 loadFilmsFromApiuseEffect 中的 searchText
    • 我已将它作为依赖列表的一部分传递,因为它是在使用效果中调用的依赖之一。您可以在这篇文章中找到更多信息 - overreacted.io/a-complete-guide-to-useeffect
    • 设置时,'setFilms([]);'在searchFilm 中,下一个渲染周期会有一个空列表,与请求返回的时间相比,这将是非常快的。所以行为应该几乎相似
    【解决方案2】:

    不完全清楚您的问题是什么,但听起来您想在向 api 发起查询之前清除电影状态?我也不清楚这里使用 useRef - useRef 只是一种获取元素引用的方法,因此以后很容易访问它 - 比如获取对 div 的引用并能够通过 @ 轻松访问它987654322@

    const = myDivRef = useRef;
    ...
    <div ref={myDivRef}/>
    

    如果是这种情况,那么我只需在 API 调用的返回中设置一次电影的状态。 WRT 到 refs,看起来你这应该只是普通变量,或者你的函数中可能的状态项。

    更新: 在这里清除目标后,您可以简单地向loadFilmsFromApi 添加一个参数来确定您应该追加还是覆盖:

    const loadFilmsFromApi = (append) => {
        getFilmsFromApiWithSearchedText(searchText, page + 1).then((data) => {
            page.current = data.page;
            totalPages.current = data.total_pages;
    
            if (append) {
                setFilms({
                    films: this.state.films.concat(data.results)
                });
            } else {
                setFilms({
                    films: data.results
                });
            }
    
            setIsLoading(false);
        })
    };
    

    【讨论】:

    • useRef 的用例之一是访问 DOM 元素。另一个用例是当您想要跟踪可变引用时
    • 以前从来没有这样用过 - 感谢您的评论
    • 是的,我想在向 api 发起查询之前清除电影状态,但仅限于我的 searchFilm 方法,因为它用于新搜索。当我在loadFilmsFromApi 中更新films 时,我只是在添加电影,所以我不想清除列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-02
    • 2020-09-15
    • 1970-01-01
    • 2020-04-08
    • 2020-07-30
    • 1970-01-01
    • 2022-10-12
    相关资源
    最近更新 更多